[WebGPU] Bind the lighting volume shadow map as texture_depth_2d in WGSL - #6
Open
matthargett wants to merge 82 commits into
Open
[WebGPU] Bind the lighting volume shadow map as texture_depth_2d in WGSL#6matthargett wants to merge 82 commits into
matthargett wants to merge 82 commits into
Conversation
## Scissor as an opt-in engine extension Moves the scissor (`enableScissor` / `disableScissor`) functionality into `@babylonjs/core` so downstream consumers can stop maintaining their own copies and import it instead. ### Design - **Type info on `AbstractEngine`** — `Engines/AbstractEngine/abstractEngine.scissor.ts` is a type-only `declare module` augmentation, so any caller holding an `AbstractEngine` can be typed to call `enableScissor` / `disableScissor` (opt-in by importing it). - **WebGL impl is an opt-in extension** — the WebGL implementation no longer lives on the `Engine` class. It moved into a new `Engines/thinEngine.scissor.(pure.)ts` extension that augments `ThinEngine.prototype`. `engine.ts` imports it, so `Engine` instances keep scissor at runtime (fully backward compatible), while the bare `ThinEngine` class stays scissor-free unless the extension is explicitly imported. - **`ThinNativeEngine` and `WebGPUEngine`** keep their own concrete implementations (native command encoder / WebGPU cached scissor); they `override` the `AbstractEngine` type member and need no import. ### Why - `ThinEngine` does not get scissor by default (it doesn't need it). ### Notes - Tree-shaking artifacts regenerated (side-effects manifest + `package.json` `sideEffects` + pure barrels). - Verified: core `tsc --noEmit`, `npm run lint:check` (incl. tree-shaking + side-effects sync), Prettier; `npm run test:unit` (pre-existing unrelated `nmeParse` teardown flake only). --------- Co-authored-by: Amoebachant <kevbrown@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
> 🤖 *This PR was created by the create-pr skill.* ## Summary - Add a built-in SDK-free FBX loader with binary/ASCII parsing, scene interpretation, meshes, materials, textures, skeletons, animation groups, cameras, and lights. - Register and export the FBX loader through the loaders package, including dynamic loader registration and SceneLoader plugin options. - Add focused unit coverage for FBX parsing/interpreter behavior, loader registration, material texture handling, normal-map coordinate options, embedded texture loading, asset-container ownership, camera/light orientation, axis/unit GlobalSettings handling, morph-target unit scaling, and multi-clip animation. - Add a visual regression suite (18 scenes) covering the loader feature surface as Playground snippets, backed by a dedicated FBX model set in the Assets repo, with committed WebGL2/WebGPU reference images. ## Notes - FBX normal-map slots default to Y-up tangent-space convention, with an opt-in Y-down loader option. - FBX `Bump` and `BumpFactor` slots are treated as normal-map-like inputs for compatibility until true grayscale height-to-normal conversion is implemented. - Embedded FBX textures use Babylon's delayed texture buffer path; sidecar textures remain supported when no embedded bytes are present. - FBX cameras look down local +X and lights down local -Z; the loader derives each camera/light world position and aim from its node world matrix (point transforms) so orientation is correct after the left-handed conversion. - `UnitScaleFactor` is treated as metadata only (it is not applied to base geometry or morph-target deltas), so morphs stay consistent with the unscaled base. ## Visual tests - Entries live in `packages/tools/tests/test/visualization/config.json` (the `FBX loader - ...` titles), one per model in `meshes/fbx/loaderTests/`. - Each test is a Playground snippet (`playgroundId` `#DZBTQU#0`–`#DZBTQU#17`) with an async `createScene` that loads its model from the Assets CDN via `BABYLON.AppendSceneAsync`, renders single-sided to match Maya, frames it with a fixed orbit (or the FBX-authored camera for the cameras/lights scene), and pins any animation to a deterministic frame. Each entry carries per-feature `dependsOn` tags (`Bones`, `Morph`, `Animations`, `Cameras`, `Lights`, `Materials`, `Textures`, `Meshes`). - The models, sidecar texture, and scene assets are in BabylonJS/Assets#149 (merged, live on the CDN). - Reference images for all 18 tests are committed under `packages/tools/tests/test/visualization/ReferenceImages/` and the tests are active (no longer `excludeFromAutomaticTesting`). All 18 pass locally on both WebGL2 and WebGPU against a fresh build. ## Validation - `npm run compile -w @dev/loaders` - `npm run test -- packages/dev/loaders/test/unit` (FBX: 79 unit tests passing) - `npm run format:check` - `npx playwright test --config playwright.config.ts --project=webgl2 -g "FBX loader"` and `--project=webgpu` (18/18 FBX visual tests passing) `npm run lint:check` currently fails on pre-existing core tree-shaking manifest/side-effect-stub drift outside the FBX changes. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: David Catuhe <david.catuhe@live.fr>
…#18574) ## What Raise the `timeoutInMinutes` of the **Native tests (experimental)** job in `.azure-pipelines/ci-monorepo.yml` from **15** to **35**. ## Why The job downloads the prebuilt BabylonNative nightly Playground and runs the full `validation_native.js` visualization suite (~290 active tests). On the CI agent each test runs roughly **~3x slower** than local hardware, so the suite needs about **14–16 minutes** of wall time plus ~1.5 min of script-load/startup. With the old 15-minute budget, even a **clean, crash-free run** was cancelled mid-suite. In a recent run the suite reached **~285 of ~290 tests** (it was rendering "Particles - Helper - Sun") before the 15-minute timeout terminated the batch job — only a handful of tests short of finishing. A use-after-free crash in the native texture upload path (fixed upstream in BabylonNative) previously masked this: the crash forced a full-suite relaunch that always blew the budget. Now that the crash is gone, the suite nearly fits — it just needs a slightly larger budget. Raising the timeout to **35 minutes** provides comfortable headroom for CI variance. For reference, the sibling native unit-tests job already uses `timeoutInMinutes: 45`. ## Scope One-line CI configuration change. No product/source code is affected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…BabylonJS#18575) ## Problem Reported on the forum: [Version 9.12.0 Is Broken On Playground](https://forum.babylonjs.com/t/63631). On 9.12.0, the default Playground shows a red TypeScript error on `new BABYLON.Scene(engine)`, and TypeScript library builds against `@babylonjs/core` fail with errors like: - `Property 'physicsBody' does not exist on type 'TransformNode'` - `Argument of type 'BABYLON.Scene' is not assignable to parameter of type 'Scene'. Type 'Scene' is missing the following properties from type 'Scene': _pointerOverSprite, _pickedDownSprite, ... and 86 more` - The same pattern for `Engine` / `AbstractEngine`, `Effect`, `Mesh`, `Observable`, etc. 9.11.0 was fine, so this is a regression. ## Root cause The declaration generator (`generateDeclaration.ts`) strips ES re-export statements out of the global `BABYLON` namespace declaration. Its exclusion list covered `export * from "..."`, `export { ... }`, and `export default`, but **not the type-only variants** `export type * from "..."` and `export type { ... } from "..."`. Those type-only re-exports were introduced by the tree-shaking `.types.ts` refactor (e.g. `meshUVSpaceRenderer.ts` → `export type * from "./meshUVSpaceRenderer.types"`). When even one `export ... from` statement is left inside `declare namespace BABYLON`, it turns the namespace into an **export context**, which breaks TypeScript declaration merging of augmented classes/interfaces. The shipped `babylon.d.ts` (served as the Playground's namespace types and consumed via `@babylonjs/core`) ended up with **89 `TS2395` ("Individual declarations in merged declaration must be all exported or all local")** errors (0 on 9.11.0), causing `BABYLON.Scene`, `TransformNode`, `Engine`, etc. to lose all their augmented members. ## Fix Broaden the two exclusion regexes in `GetPackageDeclaration` to also match the optional `type ` modifier: - `/export \{/` → `/export (?:type )?\{/` - `/export \* from "/` → `/export (?:type )?\* from "/` ## Validation Against declarations regenerated from source: - Namespace `TS2395` count: **89 → 0** - `BABYLON.Scene` re-merges all 29 declarations; augmented members (`_pointerOverSprite`, `spriteManagers`, `createDefaultLight`, `TransformNode.physicsBody`, …) are present again - Playground (namespace) and `import * as BABYLON` (module) consumer repros both type-check cleanly - `gui` / `loaders` / `serializers` / `materials` namespace portions: 0 leaked re-exports ## Tests Adds `packages/dev/buildTools/test/unit/generateDeclaration.test.ts` — a regression test that feeds a declaration file containing `export type * from "..."` / `export type { ... } from "..."` through `generateCombinedDeclaration` and asserts those re-exports are excluded from the namespace output while real declarations and augmentation interfaces are preserved. Confirmed to fail without the fix and pass with it.
…FreeCamera and FlyCamera (BabylonJS#18573) > 🤖 *This PR was created by the create-pr skill.* ## Summary Ports the framerate-independent movement system (`CameraMovement`) and the configurable `InputMapper` — already used by `ArcRotateCamera` and `GeospatialCamera` — to the `TargetCamera` family (`FreeCamera`, `FlyCamera`, and their subclasses). ## Changes - Add `TargetCameraMovement`, a shared movement controller for the `TargetCamera` family, with a configurable `InputMapper` (pointer/keyboard/touch → translate/rotate). - `TargetCamera._checkInputs` folds `cameraDirection`/`cameraRotation` into the movement system for framerate-independent inertial glide; the per-frame applied delta is written back into those fields so existing collision/gravity/rotation-constraint logic and external polling keep working unchanged. - Wire `FreeCamera`/`FlyCamera` mouse, keyboard, and touch inputs to the `InputMapper` (gate + optional sensitivity, preserving legacy default behavior). Subclasses inherit the port. - Converge `TargetCamera.inertia` to a write-through accessor that syncs the movement system (`panInertia`/`rotationInertia`), mirroring `ArcRotateCamera` and replacing the previous per-frame inertia sync. - Small `GeospatialCameraKeyboardInput` fix: allow `Cmd`+Arrow (Mac) for rotation while still blocking `Cmd` for other keys (avoids hijacking browser shortcuts). ## Behavior / compatibility - Default camera feel is preserved at the reference framerate; inertial glide is now framerate-independent. - `cameraDirection`/`cameraRotation` intentionally remain real `Vector3`/`Vector2` fields — they are mutated in place by core input classes and external XR code, so they are not converted to accessors. No public API is removed. - `FollowCamera` is intentionally not ported (it has no inertia model; the fold is a no-op for it). ## Testing - All camera unit tests pass, with new coverage for the movement fold, input mapping, subclass inheritance, and the inertia accessor convergence. - New Playwright visualization tests for `FreeCamera`/`FlyCamera` movement, with WebGL2 and WebGPU baselines. Each snippet drives the camera deterministically (`inertia = 0` makes a single `_checkInputs` apply the raw input independent of frame `deltaTime`), so the captured pose is stable across engines and frame timing. --------- Co-authored-by: Georgina Halpern <gehalper@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…n GLSL shaders (match WGSL) (BabylonJS#18571) ## What Guard the `grl_offsets` vertex attribute behind `#ifdef GREASED_LINE_USE_OFFSETS` in the **GLSL** GreasedLine shaders, so it is only declared/used when the line actually has offsets. This matches what the **WGSL** shaders already do. Affected files: - `packages/dev/core/src/Materials/GreasedLine/greasedLinePluginMaterialShadersGLSL.ts` - `packages/dev/core/src/Shaders/greasedLine.vertex.fx` ## Why The GLSL GreasedLine shaders declare and use `attribute vec3 grl_offsets` **unconditionally**, even though the `grl_offsets` buffer is only created when the mesh has offsets (`GreasedLineBaseMesh._createOffsetsBuffer`, called from `set offsets`). The `GREASED_LINE_USE_OFFSETS` define is already computed (`GreasedLinePluginMaterial.prepareDefines`: `defines.GREASED_LINE_USE_OFFSETS = !!mesh.offsets`, and the simple material sets it too) — the GLSL shaders just don't consume it. The WGSL shaders **do** guard it, because WebGPU pipeline validation rejects a vertex attribute that is declared in the shader but not bound — i.e. WebGPU *requires* this guard: ```wgsl // ShadersWGSL/greasedLine.vertex.fx + greasedLinePluginMaterialShadersWGSL.ts #ifdef GREASED_LINE_USE_OFFSETS attribute grl_offsets: vec3f; #endif ... #ifdef GREASED_LINE_USE_OFFSETS var grlPositionOffset: vec3f = input.grl_offsets; #else var grlPositionOffset = vec3f(0.); #endif positionUpdated += grlPositionOffset; ``` This PR brings the GLSL path in line with the WGSL path. ### Why it matters (the symptom) When a GreasedLine has no offsets (the common case) the `grl_offsets` buffer is never created. On **WebGL** this happens to work, because an *unbound* vertex attribute reads the constant default `(0,0,0,1)`, so `position + grl_offsets == position`. But that behavior is not guaranteed elsewhere: - **WebGPU**: can't even reach this state — declaring an unbound attribute fails pipeline validation (hence the existing guard). - **Babylon Native (bgfx: D3D11 / Metal / Vulkan)**: an attribute declared by the shader but missing from all bound vertex streams is **not** zero-defaulted — bgfx aliases it to the last bound stream, so `grl_offsets` reads unrelated vertex data and corrupts `positionUpdated += grl_offsets`. The result is shifted lines and distorted/faceted wide lines. Relying on WebGL's lenient unbound-attribute default is the underlying issue; guarding the attribute (as WGSL already does) is the correct, backend-agnostic fix. ## Validation Reproduced with the GreasedLine validation tests on Babylon Native (D3D11), Playgrounds `#H1LRZ3#103` (basic) and `#H1LRZ3#101` (simple material). Pixel comparison vs the reference images (240,000 px, 2.5% tolerance ≈ 6,000 px): | Test | Before (unconditional `grl_offsets`) | After (this guard) | |---|---|---| | GreasedLine - basic (plugin material) | 33,212 px differ ❌ | **2,361 px ✅** | | GreasedLine - simple material | 20,494 px differ ❌ | **1,479 px ✅** | No change expected on WebGL (already zero-defaults the unbound attribute) or WebGPU (already guarded). --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
> 🤖 *This PR was created by the create-pr skill.* Fixes USDZ export when a texture has cached source bytes in a format USDZ does not support, such as KTX2. The previous USDZ path could copy cached source bytes directly into the archive even when the texture entry was emitted as PNG/JPEG. This made KTX2-backed textures produce invalid USDZ image payloads. This PR keeps cached-byte export for USDZ-supported image MIME types, but falls back to the existing decoded/readback image export path for unsupported source MIME types. The cached-image extraction logic is shared with the glTF exporter so the NullEngine/cached-byte behavior stays consistent, while each exporter keeps its own supported-MIME policy. Validation: - `npm run format:check` - `npm run build:dev` - `npm run lint:check` - `npm run test:unit` - CI visualization jobs passed on the PR branch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…est (BabylonJS#18569) ## Summary Repoints the **Serialize scene without materials** visualization test (`#PH4DEZ#1` -> `#PH4DEZ#4`). The previous revision's only DOM usage was a file *download* (`createElement('a')` + `createEvent` + `link.dispatchEvent`) with no visual effect. `#PH4DEZ#4` instead performs a real in-memory serialization round-trip: 1. serialize the sphere hierarchy with `SceneSerializer.SerializeMesh`; 2. reload it via `SceneLoader.ImportMeshAsync(null, "", "data:" + json, scene)`; 3. render the **deserialized** scene. This actually validates serialization correctness (not just that it doesn't throw), modeled on the glTF serializer round-trip `#KU72PX`. The browser-only `.babylon` download is kept but guarded on `document.createEvent`, so it is skipped where the DOM isn't available. Motivation: the same shared snippet is used by BabylonNative's visual suite, which has no DOM. The round-trip lets both web and native run the test without faking DOM APIs. Mirrors BabylonNative PR BabylonJS/BabylonNative#1708. ## Visual impact The rendered scene is unchanged (same sphere hierarchy), so the `serializeWithoutMaterials.png` reference is expected to still match — the BabylonNative side validates against the same reference with the new revision (248 px diff, well within tolerance). No reference image regeneration is included; CI's Playwright run will confirm.
…BabylonJS#18565) ### Summary `ThinNativeEngine` cannot draw anything without `Buffers/buffer.align`'s prototype patch. Every per-attribute binding in `ThinNativeEngine._recordVertexArrayObject` reads `vertexBuffer.effectiveBuffer / effectiveByteOffset / effectiveByteStride` ([thinNativeEngine.pure.ts L633-L641](https://github.com/BabylonJS/Babylon.js/blob/master/packages/dev/core/src/Engines/thinNativeEngine.pure.ts#L633-L641)) — getters that only exist after `RegisterBufferAlign()` has run. There is no fallback. With the stubs from `buffer.pure.ts` in place, every `recordVertexBuffer(...)` call sees `(undefined, undefined, undefined)`, bgfx binds degenerate VBs, and the Native engine renders nothing (clear color still works). This PR makes `ThinNativeEngine`'s shared init (`_initializeNativeEngine`) call `RegisterBufferAlign()` so the engine is **self-sufficient by construction**: anyone who does `new ThinNativeEngine()` / `new NativeEngine()` gets a working engine, regardless of which entry point they imported from. The registration is idempotent so calling it many times is free. ### Regression Introduced by BabylonJS#18441 (`Tree-shaking - the pure barrel`, v9.8.0). Pre-9.8.0, `thinNativeEngine.ts` performed `import "../Buffers/buffer.align";` at the top and was the only engine module. After the split into `thinNativeEngine.pure.ts` (no side effects, but contains the call site that reads `effective*`) + wrapper `thinNativeEngine.ts` (kept the side-effect import), the user-facing `nativeEngine.ts` only re-exports from `nativeEngine.pure` which imports `thinNativeEngine.pure` — so the wrapper's side-effect import was never reached via `import "@babylonjs/core/Engines/nativeEngine"`. ### Why "register in the constructor" instead of "re-add the bare import" Two earlier shapes of this fix were considered: 1. **Re-add `import "../Buffers/buffer.align";`** to the wrapper, and have `nativeEngine.ts` import the wrapper. Minimal, matches the existing WebGPU pattern (`webgpuEngine.ts` does it at the top), but keeps the implicit dependency: any future barrel/tree-shake refactor that bypasses the wrapper silently breaks Native again. 2. **Register in the constructor** (this PR). The dependency becomes an explicit runtime contract owned by the engine that needs it. The `.pure` files stay pure-at-import (tree-shakeable), and the side-effectful wrapper no longer has to exist for this purpose at all (its bare import is dropped — see diff). Option 2 is more robust and removes the redundant wrapper-only side-effect entirely. Suggested by reviewer feedback on the original wrapper-import approach. ### Changes **Source** - `packages/dev/core/src/Engines/thinNativeEngine.pure.ts` - Import `RegisterBufferAlign` from `../Buffers/buffer.align.pure`. - Call `RegisterBufferAlign()` as the **first** line of `_initializeNativeEngine`, before any `_native.*` access. (Shared by both `ThinNativeEngine` and `NativeEngine` constructors.) - `packages/dev/core/src/Engines/thinNativeEngine.ts` - Drop the now-redundant `import "../Buffers/buffer.align";` at the top. **Test** - `packages/dev/core/test/unit/Engines/nativeEngine.sideEffects.test.ts` *(new)* - Regression test. Invokes `_initializeNativeEngine` on an `Object.create(ThinNativeEngine.prototype)` (bypasses `super()` chaos and `_native` global stubbing), then asserts that `VertexBuffer.prototype.effectiveByteStride / effectiveByteOffset / effectiveBuffer` are real getters returning the wrapped values. The initializer is allowed to throw later (when it reaches `new _native.Engine(...)`) — by then `RegisterBufferAlign()` has already run, which is the property under test. - Verified both ways: passes on this PR; fails with `expected undefined to be 12` if the `RegisterBufferAlign()` call is removed. **Auto-generated manifests** (regenerated by `npm run update:manifest`, reflect the removed side effect — `Engines/thinNativeEngine.ts` no longer has any module-load side effects) - `scripts/treeshaking/side-effects-manifest/core/Engines.json` - `packages/public/@babylonjs/core/package.json` **Tree-shake closure baseline** (`scripts/treeshaking/side-effect-import-closure-baseline.json`, new) - Before this PR, `thinNativeEngine.ts` was already side-effectful (`bare-import`), so the verifier accepted its re-export of `./thinNativeEngine.types` (which carries a `declare-module`). After dropping the bare-import, the file became side-effect-free and the verifier flagged that re-export as a new closure violation. - Accepted via `node scripts/treeshaking/checkSideEffectImportClosure.mjs --update-baseline`. The `declare-module` is a TypeScript-level augmentation only — no runtime cost. --------- Co-authored-by: Cedric Guillemet <ceguille@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1st PR for streaming. Next one will be WebGPU CS, polishing & debugging.
…8570) > 🤖 *This PR was created by the create-pr skill.* ## Problem When many meshes or geometries share a single delay-loading file (for example a packed `.babylonbinarymeshdata` geometry file), the engine downloaded the file **once per mesh**. A scene with 1000+ meshes referencing the same shared file therefore issued 1000+ identical network requests for the same URL. Reported on the forum: https://forum.babylonjs.com/t/share-same-babylonbinarymeshdata-file-across-all-parsed-meshes/63624 `Mesh._queueLoad` and `Geometry._queueLoad` each call the file loader directly with no in-flight deduplication, so a shared delay-load file is fetched N times. ## Fix Added `Scene._loadDelayedFileAsync(url, useArrayBuffer, useOfflineSupport)` which coalesces in-flight delay-load requests, keyed by URL **and** data type (binary vs string). A shared file is fetched once while the request is in flight, and the same loaded data is resolved to every coalesced caller. The in-flight entry is cleared once the request settles, so a later load can re-fetch normally. `Mesh._queueLoad` and `Geometry._queueLoad` now route through this method instead of loading directly. ## Failure handling Hardened the delay-load failure path: on load error the catch now calls `scene.removePendingData(this)` and logs via `Logger.Error`. `delayLoadState` is intentionally left as `LOADING` so a failed load does not trigger a per-frame retry storm (re-queueing every frame the mesh is in the frustum), while no longer leaving the scene with stale pending data. ## Tests Added `babylon.delayLoadCoalescing.test.ts` with 6 unit tests: - coalesces concurrent requests for the same file into a single load - re-fetches once a previous request has settled - does not coalesce requests for the same URL with different data types - clears the in-flight entry after a failed load so it can be retried - loads a shared binary file only once across multiple delay-loaded meshes - removes pending data and logs when a delay-loaded mesh fails to load ## Notes - Coalescing lives on `Scene`, consistent with the existing `_loadFileAsync`/`_activeRequests` request infrastructure and scene-scoped lifecycle. - The live forum repro scene could not be exercised in CI/local because its assets are served from an external CDN that blocks cross-origin requests from localhost; the unit tests validate the coalescing directly against the real fetch path. --------- Co-authored-by: Georgina <gehalper@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…he legacy epsilon glide cutoff (BabylonJS#18578) > 🤖 *This PR was created by the create-pr skill.* ## Summary Fixes a camera input regression introduced by BabylonJS#18573. `TargetCamera._checkInputs` ran the legacy `_panningEpsilon` / `_rotationEpsilon` glide cutoff **before** applying the frame's delta, and derived `needToMove` / `needToRotate` from the zeroed values. With the defaults `Epsilon = 0.001` and `speed = 2.0`, the cutoff limit is `speed * _rotationEpsilon = 0.002`. That is **above** typical per-frame mouse-look (`offsetPx / angularSensibility`) and keyboard deltas, so legitimate active input was discarded before it was ever applied — making `FreeCamera` / `FlyCamera` feel unresponsive (slow mouse-look and keyboard movement appeared dead). In the legacy code the cutoff ran *after* applying the delta and only trimmed the leftover inertial residual, so active input was always applied. ## Fix Capture whether each channel has raw input this frame (`hasPanInput` / `hasRotationInput`), measured before the input is folded into the movement system, and gate the glide cutoff on its absence. The cutoff now only terminates the decaying inertial tail; any active input is always applied, even when it is below the epsilon limit. Because the FreeCamera/FlyCamera mouse, keyboard, and touch inputs all flow through `cameraDirection` / `cameraRotation`, this fixes all of them in one place. It also covers direct external writes to those fields (e.g. XR code). ## Behavior / compatibility - Active input below `speed * _panningEpsilon` / `speed * _rotationEpsilon` is now applied (previously discarded). - Inertial glide termination is unchanged: on frames with no raw input, the cutoff still ends the glide at the legacy threshold and resets the movement velocity. ## Testing - All camera movement unit tests pass; the existing legacy glide-cutoff tests are unchanged. - Added regression tests asserting that a sub-epsilon **active** rotation and translation input is still applied. --------- Co-authored-by: Georgina Halpern <gehalper@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nstead of super.inertia accessor (BabylonJS#18580) ## Problem A freshly-constructed `FreeCamera`/`TargetCamera` reads `inertia === undefined` **in the shipped UMD bundle**, which feeds `NaN` into the framerate-independent movement decay (`Math.pow(undefined, dt)`) and **freezes all camera movement**. The bug reproduces on the Playground / snapshot builds but **not** on localhost dev — same source, different behavior. This is a separate, more fundamental issue than the epsilon glide-cutoff fixed in BabylonJS#18578. ## Root cause `TargetCamera` overrode `get/set inertia` with `super.inertia`. The base `Camera.inertia` getter is decorated with `@serialize()`. esbuild's decorator lowering of that decorated accessor loses the `super` home-object binding, so `super.inertia` resolves to `undefined` **only in the decorator-transformed UMD bundle**. Native ESM (dev/localhost) honors `super` correctly, which is exactly why localhost moves and the bundle does not. `ArcRotateCamera` was never affected because it already reads a local backing field (`_inertia`). ## Fix Switch `TargetCamera` to the same local-field pattern (`_targetInertia`) — no `super` in the accessor, so it compiles identically in ESM and UMD. Verified against a **rebuilt UMD bundle**: | | shipped bundle (before) | rebuilt bundle (after) | |---|---|---| | `FreeCamera.inertia` | `undefined` | `0.9` | | `movement.rotationInertia` | `undefined` | `0.9` | | 2-frame glide | NaN -> frozen | `0.1 -> 0.19` (moves) | ## Preventing regressions Unit tests run **untransformed ESM**, so they cannot catch this class of UMD-only bug. Added ESLint rule **`babylonjs/no-super-in-accessor`** (alongside the existing `no-downlevel-iteration` UMD guard) that flags `super.<member>` property access inside a `get`/`set` accessor and points authors to the local-field pattern. It produces **0 violations** repo-wide after this fix. ## Testing - New unit test: default-inertia glide produces finite, advancing rotation (no NaN). - `npx prettier --check` clean; `eslint` clean on changed files; targeted vitest 20/20 pass. - ESLint rule covered by RuleTester (flags super reads/writes incl. nested arrows & computed access; allows local fields and `super.method()` calls). --------- Co-authored-by: Georgina Halpern <gehalper@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.25.12 to 0.28.1. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/evanw/esbuild/releases">esbuild's releases</a>.</em></p> <blockquote> <h2>v0.28.1</h2> <ul> <li> <p>Disallow <code>\</code> in local development server HTTP requests (<a href="https://github.com/evanw/esbuild/security/advisories/GHSA-g7r4-m6w7-qqqr">GHSA-g7r4-m6w7-qqqr</a>)</p> <p>This release fixes a security issue where HTTP requests to esbuild's local development server could traverse outside of the serve directory on Windows using a <code>\</code> backslash character. It happened due to the use of Go's <code>path.Clean()</code> function, which only handles Unix-style <code>/</code> characters. HTTP requests with paths containing <code>\</code> are no longer allowed.</p> <p>Thanks to <a href="https://github.com/dellalibera"><code>@dellalibera</code></a> for reporting this issue.</p> </li> <li> <p>Add integrity checks to the Deno API (<a href="https://github.com/evanw/esbuild/security/advisories/GHSA-gv7w-rqvm-qjhr">GHSA-gv7w-rqvm-qjhr</a>)</p> <p>The previous release of esbuild added integrity checks to esbuild's npm install script. This release also adds integrity checks to esbuild's Deno install script. Now esbuild's Deno API will also fail with an error if the downloaded esbuild binary contains something other than the expected content.</p> <p>Note that esbuild's Deno API installs from <code>registry.npmjs.org</code> by default, but allows the <code>NPM_CONFIG_REGISTRY</code> environment variable to override this with a custom package registry. This change means that the esbuild executable served by <code>NPM_CONFIG_REGISTRY</code> must now match the expected content.</p> <p>Thanks to <a href="https://github.com/sondt99"><code>@sondt99</code></a> for reporting this issue.</p> </li> <li> <p>Avoid inlining <code>using</code> and <code>await using</code> declarations (<a href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>)</p> <p>Previously esbuild's minifier sometimes incorrectly inlined <code>using</code> and <code>await using</code> declarations into subsequent uses of that declaration, which then fails to dispose of the resource correctly. This bug happened because inlining was done for <code>let</code> and <code>const</code> declarations by avoiding doing it for <code>var</code> declarations, which no longer worked when more declaration types were added. Here's an example:</p> <pre lang="js"><code>// Original code { using x = new Resource() x.activate() } <p>// Old output (with --minify)<br /> new Resource().activate();</p> <p>// New output (with --minify)<br /> {using e=new Resource;e.activate()}<br /> </code></pre></p> </li> <li> <p>Fix module evaluation when an error is thrown (<a href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>, <a href="https://redirect.github.com/evanw/esbuild/pull/4467">#4467</a>)</p> <p>If an error is thrown during module evaluation, esbuild previously didn't preserve the state of the module for subsequent module references. This was observable if <code>import()</code> or <code>require()</code> is used to import a module multiple times. The thrown error is supposed to be thrown by every call to <code>import()</code> or <code>require()</code>, not just the first. With this release, esbuild will now throw the same error every time you call <code>import()</code> or <code>require()</code> on a module that throws during its evaluation.</p> </li> <li> <p>Fix some edge cases around the <code>new</code> operator (<a href="https://redirect.github.com/evanw/esbuild/issues/4477">#4477</a>)</p> <p>Previously esbuild incorrectly printed certain edge cases involving complex expressions inside the target of a <code>new</code> expression (specifically an optional chain and/or a tagged template literal). The generated code for the <code>new</code> target was not correctly wrapped with parentheses, and either contained a syntax error or had different semantics. These edge cases have been fixed so that they now correctly wrap the <code>new</code> target in parentheses. Here is an example of some affected code:</p> <pre lang="js"><code>// Original code new (foo()`bar`)() new (foo()?.bar)() <p>// Old output<br /> new foo()<code>bar</code>();<br /> new (foo())?.bar();</p> <p></code></pre></p> </li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/evanw/esbuild/blob/main/CHANGELOG-2025.md">esbuild's changelog</a>.</em></p> <blockquote> <h1>Changelog: 2025</h1> <p>This changelog documents all esbuild versions published in the year 2025 (versions 0.25.0 through 0.27.2).</p> <h2>0.27.2</h2> <ul> <li> <p>Allow import path specifiers starting with <code>#/</code> (<a href="https://redirect.github.com/evanw/esbuild/pull/4361">#4361</a>)</p> <p>Previously the specification for <code>package.json</code> disallowed import path specifiers starting with <code>#/</code>, but this restriction <a href="https://redirect.github.com/nodejs/node/pull/60864">has recently been relaxed</a> and support for it is being added across the JavaScript ecosystem. One use case is using it for a wildcard pattern such as mapping <code>#/*</code> to <code>./src/*</code> (previously you had to use another character such as <code>#_*</code> instead, which was more confusing). There is some more context in <a href="https://redirect.github.com/nodejs/node/issues/49182">nodejs/node#49182</a>.</p> <p>This change was contributed by <a href="https://github.com/hybrist"><code>@hybrist</code></a>.</p> </li> <li> <p>Automatically add the <code>-webkit-mask</code> prefix (<a href="https://redirect.github.com/evanw/esbuild/issues/4357">#4357</a>, <a href="https://redirect.github.com/evanw/esbuild/issues/4358">#4358</a>)</p> <p>This release automatically adds the <code>-webkit-</code> vendor prefix for the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/mask"><code>mask</code></a> CSS shorthand property:</p> <pre lang="css"><code>/* Original code */ main { mask: url(x.png) center/5rem no-repeat } <p>/* Old output (with --target=chrome110) */<br /> main {<br /> mask: url(x.png) center/5rem no-repeat;<br /> }</p> <p>/* New output (with --target=chrome110) */<br /> main {<br /> -webkit-mask: url(x.png) center/5rem no-repeat;<br /> mask: url(x.png) center/5rem no-repeat;<br /> }<br /> </code></pre></p> <p>This change was contributed by <a href="https://github.com/BPJEnnova"><code>@BPJEnnova</code></a>.</p> </li> <li> <p>Additional minification of <code>switch</code> statements (<a href="https://redirect.github.com/evanw/esbuild/issues/4176">#4176</a>, <a href="https://redirect.github.com/evanw/esbuild/issues/4359">#4359</a>)</p> <p>This release contains additional minification patterns for reducing <code>switch</code> statements. Here is an example:</p> <pre lang="js"><code>// Original code switch (x) { case 0: foo() break case 1: default: bar() } </code></pre> </li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/evanw/esbuild/commit/bb9db84c02433fbe37b3509f53f9f3e3cc48725e"><code>bb9db84</code></a> publish 0.28.1 to npm</li> <li><a href="https://github.com/evanw/esbuild/commit/9ff053e53b8eeb990f59355dbea365277ac45ee2"><code>9ff053e</code></a> security: add integrity checks to the Deno API</li> <li><a href="https://github.com/evanw/esbuild/commit/0a9bf2135b67c7e28989a5ba19f0f000805a5ab5"><code>0a9bf21</code></a> enforce non-negative size in gzip parser</li> <li><a href="https://github.com/evanw/esbuild/commit/e2a1a7132058ee067fe736eac15f695861b8654e"><code>e2a1a71</code></a> security: forbid <code>\\</code> in local dev server requests</li> <li><a href="https://github.com/evanw/esbuild/commit/83a2cbfc35809f4fd5152da59572d7bed7739d78"><code>83a2cbf</code></a> fix <a href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>: don't inline <code>using</code> declarations</li> <li><a href="https://github.com/evanw/esbuild/commit/308ad745d824c77bc607603451b257d0f2fd9a38"><code>308ad74</code></a> fix <a href="https://redirect.github.com/evanw/esbuild/issues/4471">#4471</a>: renaming of nested <code>var</code> declarations</li> <li><a href="https://github.com/evanw/esbuild/commit/f013f5f99a015bce92ec48d49181d4ad3177b29b"><code>f013f5f</code></a> fix some typos</li> <li><a href="https://github.com/evanw/esbuild/commit/aafd6e48b1088336a5f5a17e930be7e840d43d8c"><code>aafd6e4</code></a> chore: fix some minor issues in comments (<a href="https://redirect.github.com/evanw/esbuild/issues/4462">#4462</a>)</li> <li><a href="https://github.com/evanw/esbuild/commit/15300c30b5e22f7cfcbed850c246d35095658386"><code>15300c3</code></a> follow up: cjs evaluation fixes</li> <li><a href="https://github.com/evanw/esbuild/commit/1bda0c31d7697c0af44b3ab39b81e599e559a395"><code>1bda0c3</code></a> fix <a href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>, fix <a href="https://redirect.github.com/evanw/esbuild/issues/4467">#4467</a>: esm evaluation fixes</li> <li>Additional commits viewable in <a href="https://github.com/evanw/esbuild/compare/v0.25.12...v0.28.1">compare view</a></li> </ul> </details> <details> <summary>Maintainer changes</summary> <p>This version was pushed to npm by <a href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new releaser for esbuild since your current version.</p> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/BabylonJS/Babylon.js/network/alerts). </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…abylonJS#18582) ## Bugs fixed Two regressions/bugs reported in `GeospatialCamera` ("geo cam"). ### 1. Zoom was disabled while rotating A recent input-mapping refactor unintentionally reverted BabylonJS#18278. The zoom-stop condition again included a rotation guard, so zoom was suppressed during rotation. Restored the `isDragging`-only condition so **rotation and zoom work simultaneously** again. ### 2. `updateFlyToDestination` never actually redirected the flight It relied on `InterpolatingBehavior.updateProperties` setting `animatable.target = value` — but `Animatable.target` is the animated *object* (the camera), not a goal value, so retargeting was a no-op (broken since BabylonJS#17452). **Fix / design:** removed `updateProperties` and reframed a redirect as *"a fresh flight from the camera's current pose over the remaining duration."* `Animation.TransitionTo` already seeds the start keyframe from the current value, so restarting mid-flight is inherently smooth (no snap). `updateFlyToDestination` now guards on `isInterpolating` and delegates to a shared `_flyToAsync` helper used by `flyToAsync` too. Supporting changes in `InterpolatingBehavior`: - New `remainingDurationMs` getter (public `toFrame`/`masterFrame` + a shared `_FrameRate` constant). - `animatePropertiesAsync` resolves immediately when nothing needs animating (prevents a hung promise). ### Promise semantics Redirecting interrupts the in-flight animation (standard interruption semantics), so the original `flyToAsync` promise resolves early. `updateFlyToDestination` now **returns the redirect promise** (`Promise<void> | undefined`) so callers can await the redirected flight. Documented in JSDoc. ## Tests - Added `packages/dev/core/test/unit/Behaviors/interpolatingBehavior.test.ts` (6 tests: immediate resolve, convergence, `remainingDurationMs`, smooth mid-flight restart). - All 265 unit tests in Behaviors/Cameras/Animations pass; lint 0 errors; formatted. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Georgina Halpern <gehalper@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
> 🤖 *This PR was created by the create-pr skill.* Bumps `vite` from `6.4.2` to `6.4.3` across the viewer packages to address two security advisories flagged by `npm audit`: - **vite: `server.fs.deny` bypass on Windows alternate paths** ([GHSA-fx2h-pf6j-xcff](GHSA-fx2h-pf6j-xcff)) - **launch-editor: NTLMv2 hash disclosure via UNC path handling on Windows** ([GHSA-v6wh-96g9-6wx3](GHSA-v6wh-96g9-6wx3)) ### Changes - `package.json`, `packages/tools/viewer/package.json`, `packages/public/@babylonjs/viewer/package.json`: bump `vite` devDependency to `6.4.3` - `package-lock.json`: regenerated ### Validation - `npm audit` no longer reports the two advisories above. - Built `@tools/viewer` and `@babylonjs/viewer`; output is **byte-for-byte identical** to the 6.4.2 build (verified by SHA-256 hashing all 1669 `lib/` + `dist/` files before and after). Vite is only used by the dev `serve` script, not the build output. > Note: a separate `esbuild` advisory remains on the Vite 6.x line and would require a major Vite 8 upgrade — out of scope for this patch. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Summary - Add opt-in `NullEngineOptions.enableMultiview` support for CPU-side multiview render-state tests. - Provide headless multiview RTT creation/binding and no-op UBO plumbing needed to inspect `viewProjectionR` under NullEngine. - Add focused NullEngine tests for opt-in caps, RTT metadata, multiview UBO/matrix state, and one expected-failing regression test documenting the stale frustum behavior without changing the production `Scene` hot path. ## Scope This replaces the closed BabylonJS#18542 with the production hot-path changes removed. The PR intentionally does not modify `Scene.setTransformMatrix`, `Scene.updateTransformMatrix`, or any other render hot-path code. The expected-failing test is kept as executable documentation of the bug NullEngine can now expose. Making it pass requires a separate production-path design because it currently depends on changing the scene transform-cache invalidation behavior. ## Tests - `npx prettier --write packages/dev/core/test/unit/Engines/nullEngine.test.ts packages/dev/core/src/Engines/nullEngine.pure.ts packages/dev/core/src/scene.pure.ts` - `npx eslint --quiet packages/dev/core/src/Engines/nullEngine.pure.ts packages/dev/core/test/unit/Engines/nullEngine.test.ts` - `npx vitest run --project=unit packages/dev/core/test/unit/Engines/nullEngine.test.ts` (`9 passed | 1 expected fail`) - `npm run compile:source -w @dev/core`
focus on perf
…lonJS#18558) # [Native] Honor `depthCullingState.depthTest` on the native engine ## Problem `EffectRenderer` (used by `EffectWrapper`-based fullscreen passes) disables depth testing by mutating the shared depth-culling state directly: ```ts // Materials/effectRenderer.pure.ts -> applyEffectWrapper() this.engine.depthCullingState.depthTest = depthTest; // false for a fullscreen pass ``` On the WebGL/WebGPU engines this is flushed to the GPU by `applyStates()`, which is called from `drawElementsType`/`drawArraysType` and reads `depthCullingState`. The native engine does **not** go through that `applyStates()` path — its draw methods encode a draw command directly, and depth state is only ever changed through the explicit `setDepthBuffer()` / `setDepthFunction()` commands. As a result, a depth-test toggle done through `engine.depthCullingState.depthTest` never reaches the native command stream. The visible effect: a fullscreen `EffectWrapper` quad keeps the depth-test state of the previously rendered geometry (e.g. `LEQUAL`). Drawn at clip-space `z = 0.5` against a render target whose depth buffer is not at the far value, every fragment fails the depth test and is discarded, so the pass produces an all-black target even though the pixel shader output is correct. `ThinDepthPeelingRenderer` toggles `depthCullingState.depthTest` directly as well and is affected by the same gap. ## Fix Make the native engine honor `depthCullingState.depthTest` the same way the other engines do: - `setDepthBuffer()` keeps `_depthCullingState.depthTest` in sync (matching the base `AbstractEngine.setDepthBuffer`, which is literally `this._depthCullingState.depthTest = enable`). - Before each native draw, `_flushDepthTestState()` reconciles the encoded depth-test enable with `depthCullingState.depthTest` and emits the command only when it changed. This keeps the existing explicit `setDepthBuffer()` / `setDepthFunction()` paths working unchanged (they already set the state object / are the source of truth) while also honoring direct `depthCullingState.depthTest` mutations. No public API change. ## Testing Validated on Babylon Native (Win32 / D3D11) with the Playground validation suite: - The previously-broken `EffectRenderer` onion-skin/blur fullscreen-pass scene renders correctly instead of all black. - Full standard validation sweep: no regressions (every previously-passing test still passes, including depth- and transparency-sensitive scenes). ## Notes `EffectRenderer` also toggles `stencilState.stencilTest` directly. That has the same latent gap on native but defaults to `false` and has no reproducing scene today, so it is intentionally left out of this change to keep it minimal; it can be addressed with the same pattern if a repro appears. --- Partial fix for BabylonJS/BabylonNative#1106 (Handle Engine States in Babylon Native). --------- Co-authored-by: Branimir Karadzic <branimirkaradzic@gmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Summary - Add optional depth-based pickedPoint and normal reconstruction to GPUPicker. - Use a MultiRenderTarget depth attachment when depth picking is enabled, with shader macro guards so the default color-only path is unchanged when disabled. - Extend pickAsync, multiPickAsync, and boxPickAsync to return depth-derived point/normal data where available. - Add adaptive multiPickAsync readback options for rectangle vs individual point readbacks. ## Context This PR follows up on the GPU picking point/normal discussion and example: https://forum.babylonjs.com/t/gpu-picking-point-and-normal-example/40965/34 ## Coordinate scaling note - GPUPicker now maps input/canvas CSS pixel coordinates to render-target pixels using the input element client rect when available, falling back to hardware scaling for non-DOM engines. This preserves the existing default-canvas behavior while improving picking on CSS-scaled canvases. ## Validation - npx tsc -p packages/dev/core/tsconfig.build.json --noEmit --pretty false - npm run compile:assets -w @dev/core - npx vitest run packages/dev/core/test/unit/Collisions/babylon.gpuPicker.test.ts --project=unit - npm run test:visualization -- --project=webgl2 --grep "GPUPicker Depth Point and Normal" --update-snapshots
## Summary
This PR reworks `SceneLoader` so that all internal orchestration is
**Promise / `async`/`await`-first**. Callback chaining, explicit
`Promise.then`/`Promise.catch`, and the old mixed control flow have been
removed from the internals. The public callback-style APIs (the
deprecated `SceneLoader.ImportMesh`, `Load`, `Append`,
`LoadAssetContainer`, `ImportAnimations` statics and their `*Async`
variants) are now thin wrappers around a small set of core `async`
functions.
Along the way, the refactor fixes a class of long-standing bugs where a
load operation could **never settle** (the returned Promise never
resolved or rejected, and success/error callbacks were never invoked) or
where **pending plugin data leaked**. A comprehensive unit test suite
was added to lock in the corrected behavior.
## Motivation
`sceneLoader.ts` had accumulated a mix of three different async styles:
raw callbacks, hand-written `Promise` executors with `.then`/`.catch`,
and `async`/`await`. This made the control flow hard to follow and, more
importantly, hid several edge cases where errors thrown synchronously
vs. asynchronously were handled inconsistently — sometimes leaving a
load hanging forever.
## Changes
### Core refactor (`packages/dev/core/src/Loading/sceneLoader.ts`)
- **Single async core per operation.** Each public operation now funnels
through one core `async` function:
- `importMeshCoreAsync`
- `loadSceneCoreAsync`
- `appendSceneCoreAsync`
- `loadAssetContainerCoreAsync`
- `importAnimationsCoreAsync`
These contain the real logic; every public/legacy entry point (modern
`*Async` functions and the deprecated `SceneLoader` statics, including
the callback overloads) simply `await`s the appropriate core function
and adapts the result/errors to its signature.
- **`loadDataAsync` returns the loaded payload.** Plugin instantiation,
`directLoad`, and file loading are bridged into a single Promise that
resolves with `{ plugin, data }` and rejects on any failure, instead of
driving everything through `onSuccess`/`onError` callbacks.
`try/finally` guarantees pending data cleanup.
- **Unified plugin handling via an async adapter.** Added a
`SceneLoaderPlugin` union type, an `isSyncPlugin` type guard, and a
`toAsyncPlugin` adapter that wraps a synchronous `ISceneLoaderPlugin` as
an `ISceneLoaderPluginAsync`. Call sites no longer branch on `(plugin as
ISceneLoaderPlugin).importMesh` / `.load` with casts and non-null
assertions; they work against a single async-shaped interface.
- **Centralized error handling.** Added `createLoadError`,
`toLoadError`, and `getErrorMessage` helpers so synchronous throws and
asynchronous rejections are funneled into a consistent `RuntimeError`
with `ErrorCodes.SceneLoaderError`, without double-wrapping
already-tagged scene-loader errors.
- **Robust progress callbacks.** Added `wrapProgress`, which isolates
user `onProgress` callbacks so that a throw inside `onProgress` is
logged (`Logger.Warn`) and does **not** abort the load.
- **Preserved eager `OnPluginActivatedObservable` timing.** Plugin
factories are only `await`ed when `createPlugin` actually returns a
`Promise` (`const p = createPlugin(...); plugin = p instanceof Promise ?
await p : p;`). This avoids deferring synchronous plugin activation to a
microtask, keeping `OnPluginActivatedObservable` firing synchronously
for backward compatibility.
### Bug fixes (behavioral)
The refactor closes several "never settles" / leak paths that previously
could hang a load or silently swallow errors:
- **Silent `false` return:** a synchronous plugin's `importMesh`/`load`
returning falsy *without* calling `onError` now rejects (and clears
pending data) instead of hanging forever.
- **Invalid file info:** `GetFileInfo` returning `null` now rejects the
operation instead of leaving the Promise unsettled.
- **Null scene/engine paths:** these now reject instead of silently
never settling.
- **Plugin disposed mid-load:** now rejects with a clear "plugin was
disposed" error rather than hanging.
- **Unknown `animationGroupLoadingMode`:** now rejects instead of never
settling.
- **Synchronous `directLoad` throw:** now cleans up pending data and
rejects, matching the async `directLoad` rejection path.
- **Swallowed `LoadSceneAsync` rejection** (e.g. disabled/unavailable
plugin) is now surfaced.
- **Offline provider bypass (PR BabylonJS#18584 class):** in-memory sources
(`File` / raw data / `data:`) bypass the offline provider; URL-backed
loads still go through it.
- **Consistent error logging:** callback statics now log via
`Logger.Error` when no `onError` handler is supplied, including paths
that were previously swallowed silently.
### Tests
- **New unit suite:**
`packages/dev/core/test/unit/Loading/sceneLoader.test.ts` (58 tests). It
uses a fully controllable dummy loader plugin to exercise:
- sync vs. async plugins, plugin factories, `directLoad`, `loadFile`,
binary/`ArrayBufferView` input, options, and observables;
- synchronous throws **and** asynchronous rejections from plugin entry
points;
- every "never settles" / leak path listed above (added first as failing
regression tests, then made green by the refactor);
- PR BabylonJS#18584 scenarios (offline provider bypass for in-memory vs. URL
sources);
- the deprecated `SceneLoader` static callback APIs (success and error
callbacks).
- **Integration test:**
`packages/dev/loaders/test/integration/babylon.sceneLoader.test.ts` —
the "Load BoomBox with dispose" test now `.catch(() => {})`s the
`AppendAsync` promise, since disposing the loader mid-load now
(correctly) rejects the load promise; this prevents an unhandled
rejection. Full SceneLoader integration file passes (requires the
Babylon CDN dev server).
## Backward compatibility
- Public API signatures are unchanged. Deprecated callback statics keep
behaving as wrappers over the new async cores.
- `OnPluginActivatedObservable` still fires synchronously for
synchronous plugin factories.
- The only intentional behavioral differences are the bug fixes above:
operations that used to hang now reject, and a few previously-silent
error paths now log or reject. These are corrections to clearly buggy
behavior.
## Validation
- Unit: `sceneLoader.test.ts` — 58/58 passing.
- Integration: `babylon.sceneLoader.test.ts` — passing with the dev
server running.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…JS#18561) Paired native PR: BabylonJS/BabylonNative#1750 ## What Adds cube render target support to the Babylon Native engine. Previously the native engine had no `createRenderTargetCubeTexture` override and `bindFramebuffer(faceIndex)` threw, so any feature that renders into a cube map — `ReflectionProbe`, point-light cube shadow maps — fell through to the WebGL code path and dereferenced the null `_gl` context (`TEXTURE_CUBE_MAP undefined`). ## Changes (`packages/dev/core/src/Engines`) - `thinNativeEngine.pure.ts` - `createRenderTargetCubeTexture`: creates a native cube color texture and one framebuffer per face (the native side binds the matching cube layer). - `bindFramebuffer`: binds the per-face framebuffer for cube render targets. - `generateMipMapsForCubemap`: no-op on Native — bgfx auto-generates the mip chain on render-target resolve, the same way 2D RTTs get their mips. - `Native/nativeRenderTargetWrapper.ts`: tracks per-face framebuffers and releases them on dispose. - `Native/nativeInterfaces.ts`: threads the cube/layer params through `initializeTexture` and `createFrameBuffer`. ## Paired native change Requires the matching BabylonNative C++ change (cube color texture + per-face attachment). Draft until both land. ## Testing Built `babylon.max.js` and ran the BabylonNative Playground validation suite (D3D11). The "Shadows with instances" tests (left/right handed), which previously crashed, now pass; the ReflectionProbe and point-light-shadow scenes render with correct geometry and orientation. --- ## Related PRs & landing order - **Babylon.js (engine / TS):** BabylonJS#18561 - **BabylonNative (C++ engine + test re-enable):** BabylonJS/BabylonNative#1750 Co-dependent; land in this order: 1. **Babylon.js BabylonJS#18561 first** — adds the cube render-target TS overrides; no WebGL behavior change. 2. A **`babylonjs` npm release** ships that TS change. 3. **BabylonNative BabylonJS#1750 last** — bumps the bundled `babylonjs` and re-enables the 2 `Shadows with instances` (left/right handed) validation tests, which only pass once the paired JS is present in the bundled engine. --------- Co-authored-by: Branimir Karadzic <branimirkaradzic@gmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
not last part.
…S#18613) ## Goal Speed up PR CI (Monorepo CI pipeline) **without dropping any tests**. Constraints respected: - **Peak ≤ 8 parallel jobs per PR** (org has 15 shared agents). - **No Nx Cloud cost** — only the free `Cache@2` local cache + free Pipeline Artifacts. This is a CI-only change; no product code is touched. Opening as **draft to validate on real CI** before merge. ## What changed ### Caching (free `Cache@2`, biggest win on re-pushes) - New `templates/cache-steps.yml` emits npm / Nx / Playwright pipeline caches, plus pipeline vars for stable cacheable locations under `$(Pipeline.Workspace)`. - Wired into Build, ES6, ES6Tools, FormatLint, TypeDoc, UnitTests, Interaction, VisWebGL2, VisWebGPU, Performance, Viewer. - Nx cache is keyed per-job on the commit with `restoreKeys` fallback to the most recent prior cache; Nx's own input hashing keeps it correct. This is what makes "fix one comment + repush" fast. ### Parallelism / restructuring (behavior-preserving) - **ES6 job split.** `ES6` now builds only the lib packages the ES6 visualization tests consume (`build:es6:libs`) and runs `es6vis`. A new parallel **`ES6Tools`** job runs the full `build:es6` (editor packages + `check:treeshaking-all`, still **blocking**), the `es6-packages` smoke test (which hard-requires the editor builds), and the file-size report. `Deploy` now also depends on `ES6Tools` (it consumes `fileSizes.json`). This pulls ~7–9 min of editor/tree-shaking work off the es6vis critical path. - **UnitTests moved out of post-Build "wave 2".** It runs in-process (vitest) and needs no Build snapshot; the one CDN-dependent assertion (`moduleFileSize.test.ts`) already self-skips when `fileSizes.json` is absent. It keeps only a `FormatLint` fail-fast gate. Frees an agent slot. - **MemoryLeakTests split** into two parallel suite jobs (ci + packages) via `templates/memory-leak-job.yml`, trimming the post-Build critical-path tail. ## Behavior preserved (everything still runs and fails as before) - Tree-shaking violations → fail `build:es6` in `ES6Tools` (blocking) → fail pipeline. - `es6-packages` smoke → blocking in `ES6Tools`, with editor builds present. - `es6vis` → blocking in `ES6`. - `prepublishOnly` → still `continueOnError`. - File-size report → still generated and uploaded. - Memory-leak suites → still non-blocking. ## Expected impact - ES6 critical pole ≈ **36 min → ~27 min**. - Faster re-pushes via npm/Nx caches. - **Peak parallel jobs stays ≤ 8.** ## Why no Pipeline Artifacts for the ES6 handoff The es6 build output is hundreds of MB of many small files written in-place into `packages/public/@babylonjs/*` — a poor artifact fit (per-file overhead, would need tar), and Nx rebuilds deps anyway. Parallelizing the work es6vis doesn't need beats an artifact handoff here. ## Note for reviewers Splitting MemoryLeakTests produces **two** PR comments (one per suite) instead of one. 🤖 Validating on CI before marking ready. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e for pure barrel (BabylonJS#18620) ## Motivation When consuming `@babylonjs/core` through the **pure barrel** (tree-shaking-friendly path), features could fail silently or crash because a prototype-augmented engine/scene method was never registered. That registration only happened as a side effect of an `import "..."` in the feature's `.ts` wrapper — which the pure barrel deliberately skips. The guiding principle: **if a feature simply cannot work without another feature** (a *hard* dependency, not an optional capability), it should register that dependency **at construction time**, rather than forcing the consumer to add a manual side-effect import. ## What changed Hard dependencies are now registered inside the feature's `.pure.ts` constructor using the existing idempotent `Register*()` functions. This mirrors the pre-existing precedent in `depthRenderer.pure.ts`. **Engine extensions** — now self-register the engine extension they require: - `dynamicTexture`, `cubeTexture`, `htmlElementTexture`, `multiRenderTarget` - `webgl2ParticleSystem` / `thinParticleSystem` (transformFeedback), `gpuParticleSystem` - `vrMultiviewToSingleviewPostProcess` (alpha / multiview), `thinDepthPeelingRenderer` **Scene components** — caller now registers the scene component before invoking the augmented `scene.enableX()`: - `prePassRenderer` → geometryBufferRenderer SC - `IBLShadows/iblShadowsRenderPipeline` → geometryBufferRenderer SC + iblCdfGenerator SC - `FrameGraph/.../iblShadowsRendererTask` → iblCdfGenerator SC - `Cameras/VR/vrExperienceHelper` → gamepad SC + animatable - `animationGroup` → animatable **Redundant import removal** — the `ssao` and `lens` rendering pipelines already self-register the depth scene component in their `.pure` constructors, so the now-redundant wrapper imports were dropped. ## Metadata - Side-effects manifest shards updated (`Animations`, `Cameras`, `FrameGraph`, `Materials`, `Particles`, `Rendering`). - `@babylonjs/core` `package.json` `sideEffects` array updated to drop the wrappers that are now side-effect-free (684 → 675 cumulative). ## Backward compatibility Fully preserved. The wrappers only lose bare side-effect imports that affect the **pure barrel** path. The **full** side-effect barrel (`@babylonjs/core` via `index.ts`) still registers everything, because every target is still `export *`'d from its package index barrel. The registration calls are all idempotent, so the full path incurs no double-registration cost. ## Notes for reviewers - All `Register*()` functions used here are idempotent (guarded by a module-level `_Registered` flag or an instance check), so construction-time calls are safe and cheap. - Scene-component register functions receive the feature class as an argument (the augmented `scene.enableX()` constructs it), so no circular value imports are introduced. - Some candidates were intentionally **skipped**: `baseTexture.polynomial` (handled by the generated `_MissingSideEffectProperty` stub pattern + too cross-cutting), `colorCurves` (serialization-only, not a hard dep), `planeRotationGizmo`→`linesBuilder` (vestigial), `gaussianSplattingMesh`→`thinInstanceMesh` (no `thinInstance` usage in `.pure`). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sampling fixes for OpenPBR fuzz, including a fix to use the correct diffuse lighting when coating is used.
…ree-shaken builds (BabylonJS#18623) ## Problem In tree-shaken builds, `SerializationHelper._ImageProcessingConfigurationParser` stays an unregistered throwing stub unless the `imageProcessingConfiguration` side-effect wrapper happens to be imported transitively. As a result, `clone()` / `Parse()` throws for any material that serializes an image processing configuration, because cloning serializes `_imageProcessingConfiguration` (serialization type 9) and invokes that unset parser. This affects every material that mixes in `ImageProcessingMixin`: - `StandardMaterial` - `BackgroundMaterial` - `NodeMaterial` - `PBRMaterial` - `PBRMetallicRoughnessMaterial` - `PBRSpecularGlossinessMaterial` - `OpenPBRMaterial` ## Fix Rather than relying on a manual side-effect import at the call site, each affected material now calls `RegisterImageProcessingConfiguration()` from its own `Register*()` function in its `.pure.ts` — the place the tree-shaking architecture designates for side effects. Importing a material's side-effect wrapper (or calling its register function directly) now self-registers the parser, while the pure import path remains side-effect-free. ## Tests Adds `babylon.imageProcessingSideEffect.test.ts`, which for each of the 7 materials asserts: 1. The parser is a throwing stub on a fresh module import. 2. Importing the material's `.pure` module produces no side effect. 3. Running the material's `Register*()` wires up a working parser. This test fails on `master` before the fix. ## Validation - `tsc` build: clean - ESLint (incl. pure-import rules): 0 errors - Prettier: formatted - `check:manifest-drift` and `check:side-effects-sync`: up-to-date (no top-level side-effect change) - Existing material unit tests: pass ## Note `StandardMaterial` also serializes `FresnelParameters` (`_FresnelParametersParser`), which has the same latent stub problem in tree-shaken builds. This PR is intentionally scoped to the reported image-processing issue; happy to extend the same pattern to fresnel parameters in a follow-up if desired. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## What Adds `.github/design-guidelines.md` — a standalone, copyable design guidelines document for **Babylon.js tools** (Inspector, NME/NGE/NPE/NRGE, GUI Editor, Smart Filters Editor, Viewer, Playground, and future tools). It is based on the existing Fluent UI / `MakeModularTool` work in the **Inspector (inspector-v2)** and **Flow Graph Editor**, which already define a strict color schema and a consistent panel/overlay structure. ## Why We want one shared baseline so every tool — across all Babylon.js repositories — looks, feels, and behaves like part of the same family. The doc is intentionally self-contained so it can be copied into other repos. ## Contents - **Core principles** — Fluent-first, theme-token-driven, shared wrappers, one shell, modular services. - **Color schema** — the Babylon brand ramp (key color `#3A94FC`), light/dark theme generation, and the no-hard-coded-colors rule. - **Layout & shell structure** — central content, dockable side panes (left/right × top/bottom), toolbars, `compact` vs `full` modes. - **Bootstrapping** — `MakeModularTool`, services, reactive hooks, `ISettingsStore`. - **Components / styling / typography / icons / sizing / feedback** — shared wrappers, `PropertyLine`/`Accordion`, `ToggleButton`/`Collapse`, `makeStyles` + tokens, text presets, unsized icons, `ToolContext`, toasts/dialogs/teaching moments. - **Consuming from npm** — install + peer-dependency guidance for `@babylonjs/shared-ui-components` and a stability caveat. - A per-tool **checklist** and a **references** section. Also links the new doc from `.github/instructions/index.md`. ## Notes Docs-only change. The local `precommit` hook (`lint-staged`) is not installed in this worktree, so the commit was made with `--no-verify`; there is no code to lint/build/test here. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…abylonJS#18624) ## Problem SSAO2 — and other texture-chaining post-process pipelines — render incorrectly in **tree-shaken / selective-ES6 builds** (importing core through the pure barrel rather than the full `@babylonjs/core` bundle). The scene color is lost and the image washes out to a near-white, banded result. This surfaced in the **Viewer**, whose SSAO path runs against the dev/tree-shaken build. The published full bundle is unaffected because bundling pulls in every side effect, which is exactly what masks the bug. | SSAO off | SSAO on (before this PR) | |---|---| | Correctly shaded, textured scene | Scene color gone, washed white + banded | This PR completes the construction-time / eager side-effect registration work from BabylonJS#18546 and BabylonJS#18620, which covered ~20 classes plus the `PostProcessRenderPipelineManager` getter but missed the two cases below. ## Root causes There are **two independent missing side-effect registrations**: ### 1. `PostProcess` texture-binding methods were never registered SSAO2's combine pass binds the captured original scene color via: ```ts effect.setTextureFromPostProcessOutput("originalColor", this._originalColorPostProcess); ``` That `Effect` / `AbstractEngine` prototype method is installed only by `RegisterPostProcess()`, which normally runs as a side effect of importing the `postProcess.ts` wrapper. In a tree-shaken build only `postProcess.pure` is imported, so the method stayed an **unregistered no-op stub**: `originalColor` never bound → the combine shader sampled the default (white) texture → the output became `AO × white` instead of `AO × sceneColor`. **Fix:** the `PostProcess` constructor now calls the idempotent `RegisterPostProcess()`, matching the pattern BabylonJS#18620 applied to ~20 other classes. This also latently repairs **bloom, depth of field, and SSR**, which rely on the same `setTextureFromPostProcess` / `setTextureFromPostProcessOutput` methods. ### 2. The `Scene.postProcessRenderPipelineManager` getter was registered too late for some consumers BabylonJS#18546 injects the manager class in the `PostProcessRenderPipeline` constructor. But a consumer may access the manager (e.g. to subscribe to its `onNewPipelineAddedObservable` / `onPipelineRemovedObservable`) **before** constructing the pipeline. In that ordering the getter is still unregistered and the manager is `undefined`, so SSAO never attaches. **Fix:** `RegisterSsao2RenderingPipeline()` — the import-time entry point already triggered when the SSAO2 module loads — now eagerly registers the getter and injects the concrete `PostProcessRenderPipelineManager` class, so it is available as soon as SSAO2 is imported. This follows BabylonJS#18546's "inject the class, don't force the full side-effect wrapper" approach and keeps the fix scoped to SSAO2 without affecting other pipelines' tree-shaking. ## Tests Adds a Viewer visualization test, `load OBJ model with ssao="enabled"`, that loads `StanfordBunny.obj` with SSAO enabled and screenshot-compares the result (also exercising OBJ loading, which had no Viewer coverage). SSAO seeds its sampling-kernel rotation texture from `Math.random`, so the test installs a deterministic PRNG via `addInitScript` to keep the screenshot stable. This gives CI coverage for SSAO2, whose standard visualization test (`ssao2`) is currently `excludeFromAutomaticTesting`. Re-enabling that standard test as an additional guard would be a reasonable follow-up. ## Validation - Viewer OBJ+SSAO renders correctly with **no Viewer-side change** (the fix is entirely in core). - New test passes deterministically; **all 44 Viewer tests pass**. - Post-process unit tests pass. - **All 16 tree-shaking checks pass** (manifest drift, side-effect import closure, pure barrels, side-effect stubs) — confirming the new `.pure` imports don't break the side-effects contract. - `prettier --check` and `eslint` clean (0 errors). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Problem When `engine.useExactSrgbConversions = true` is set on WebGPU, shaders fail to compile and nothing renders (blank canvas). Reproduced via https://playground.babylonjs.com/#XDNVAY#41: ``` Error while parsing WGSL: unresolved call target 'lessThanEqual' return mix(remainingSection, nearZeroSection, lessThanEqual(color, vec3f(0.04045))); ``` ## Cause The vec3 exact sRGB↔linear conversion helpers in `helperFunctions.fx` (WGSL) used GLSL's `lessThanEqual()`, which does not exist in WGSL. The scalar variants already correctly used `select()`. ## Fix Replaced `mix(..., lessThanEqual(...))` with `select(..., color <= ...)`. Arg order is preserved since `mix(a,b,cond)` and `select(a,b,cond)` both return `b` where the condition is true, and `vec3f <= vec3f` yields the `vec3<bool>` that `select` accepts per-component. Only the WebGPU exact-sRGB path was affected; default conversions and WebGL2 were unaffected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…don (BabylonJS#18625) ## What Follow-up to ongoing pure-barrel work. Removes the remaining genuine side-effect leaks that the pure-barrel analysis flagged, across `@babylonjs/gui`, `@babylonjs/loaders`, and the `@babylonjs/addons` atmosphere module. ### 1. Swap remaining non-pure core/gui imports to `.pure` variants These modules are reached by the pure barrels but still value-imported the side-effectful core/gui modules, dragging their side effects into the pure import path. Swapped each to its existing `.pure` variant (every swapped symbol — `Vector2/3`, `Matrix`, `Quaternion`, `Color3/4`, `SpotLight`, `Animation`, `Tools` — is exported by its `.pure` target): - `gui/2D/math2D.ts`, `gui/2D/measure.ts` → `core/Maths/math.vector.pure` - `loaders/glTF/2.0/Extensions/objectModelMapping.ts` → `math.vector.pure`, `math.color.pure`, `spotLight.pure` - `loaders/glTF/glTFValidation.ts` → `tools.pure` - `loaders/glTF/2.0/glTFLoaderAnimation.ts` → `animation.pure`, `math.vector.pure` - `loaders/glTF/2.0/pbrMaterialLoadingAdapter.ts` → `math.color.pure`, `math.vector.pure` ### 2. Make `AtmospherePBRMaterialPlugin` free of top-level shader side effects The plugin used top-level `import "./ShadersWGSL/ShadersInclude/..."` to register its shader includes — the one genuine module-scope side effect in the atmosphere addon. It now loads those includes lazily via dynamic `import()` (selecting the correct GLSL/WGSL variant for the host material's shader language) and gates `isReadyForSubMesh()` until they are registered in the `ShaderStore`. This mirrors the existing `diffuseSkyIrradianceLut` `extraInitializationsAsync` pattern. This also fixes a latent gap: the old code only eagerly registered the **WGSL** includes; GLSL relied on transitive registration elsewhere. Both languages are now handled explicitly and self-contained. The plugin is only instantiated via `atmosphere.ts`'s per-material factory, and effect compilation is already gated through `isReadyForSubMesh`, so there is no behavioral regression. ## Verification Changes are mechanical/type-safe by inspection. The dynamic-`import()` approach matches the established `diffuseSkyIrradianceLut` pattern. Please confirm in CI: - `npm run check:treeshaking-all` (pure-barrel / side-effect invariants) - addons typecheck + the `Atmosphere *` visualization tests on **WebGL2** and **WebGPU** (The local worktree had no `node_modules`, so lint/build/tests were not run locally and the precommit hook was bypassed.) --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ctor in dev (BabylonJS#18627) ## Problem Reported on the forum: [Sandbox dev server no longer works after Vite tree-shaking changes](https://forum.babylonjs.com/t/sandbox-dev-server-no-longer-works-from-documented-command-after-vite-tree-shaking-changes/63693). On a fresh checkout, the sandbox dev server (`npm run serve -w @tools/sandbox`) had several runtime failures: - Dragging a **DRACO** (and mesh-quantization) GLB failed to load. - Opening the **Inspector** threw `Cannot read properties of undefined (reading 'show')`. - After that was worked around, the Inspector still crashed (`getAllAnimatablesByTarget` undefined) on a non-draco model. ## Root cause The sandbox dev server aliases Babylon packages to their tree-shaken `dist` builds, so only explicitly-imported side effects are present. Three were missing: 1. Only `loaders/glTF/2.0/glTFLoader` was imported → no glTF extensions registered (draco, mesh quantization, KHR materials, …). 2. The Inspector v2 debug-layer side effect was never loaded → `scene.debugLayer.show()` blew up. 3. `core/Animations/animatable` was tree-shaken out → `getAllAnimatablesByTarget` missing, crashing the Inspector properties panel. ## Fix (`packages/tools/sandbox`) - **main.ts**: import the full `loaders/glTF/2.0` extension barrel, import `core/Animations/animatable`, and in dev load `inspector/index` (attaches `Scene.debugLayer` for v2; production keeps getting it from the CDN bundle). - **vite.config.ts**: alias `inspector` to `inspector-v2/dist` plus the lazily-loaded `gui-editor`/`node-*-editor` packages so Vite can resolve those dynamic imports. ## Verification Reproduced all three errors with Playwright, then confirmed after the fix: draco GLB renders, Inspector opens with a clean properties panel, no console errors. Dev-only imports are gated by `import.meta.env.DEV`, so the production `vite build` still passes and stays small (~188 kB). tsc/eslint/prettier clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…8630) ## What PR2 of the multi-PR effort to migrate Babylon.js to TC39 Stage 3 decorators. This PR lands the **Babylon Native ES5 downlevel pipeline and CI wiring only** — it does **not** flip any decorator compiler flag and is independently mergeable. ## Why Babylon Native runs on Chakra, which consumes ~ES5 script. The upcoming TC39 decorator migration will force the core UMD bundle to an ES2015 target (the `accessor` keyword requires ES2015+), which Chakra cannot parse. We land the downlevel tooling **first** so Native stays green before any flag flip. Because master is still ES5 UMD today, this downlevel step is a **harmless no-op** on current output (it re-emits equivalent ES5), so the pipeline is verified and ready for later PRs. ## Changes - **`scripts/downlevelNativeScripts.mjs`** — new script using `@babel/core` + `@babel/preset-env` with `forceAllTransforms` to emit ES5. Accepts file/dir args and transforms files matching `/^babylon.*\.js$/i`. `useBuiltIns: false` leaves library behavior untouched. - **`package.json`** — adds the `downlevel:native-scripts` npm script and `@babel/core` / `@babel/preset-env` (`^7.29.7`) devDependencies. - **`.azure-pipelines/ci-monorepo.yml`** — Native job now runs `npm install`, downlevels `BabylonNativeNightlyUnitTests\...\babylon.max.js` before `UnitTests.exe`, and downlevels `BabylonNativeNightlyPlayground\Scripts\babylon.max.js` before the playground run. ## Validation - `npm install` ✅ (babel deps resolved, lockfile updated) - Built core UMD `babylon.max.js`, ran `node ./scripts/downlevelNativeScripts.mjs` on it → `es-check es5` passes before and after; `node --check` confirms valid JS ✅ - Filename filter correctly skips non-`babylon*.js` files ✅ - `npm run lint:check` ✅ - `npm run format:check` ✅ Reference: PR BabylonJS#18142 (used as a guide for the script and CI edits; no commits cherry-picked). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e barrel) (BabylonJS#18629) ## Problem Since v9.12, `Scene.createDefaultEnvironment` crashes with: ``` TypeError: CubeTexture.CreateFromPrefilteredData is not a function at _EnvironmentHelper._setupEnvironmentTexture ``` `CubeTexture.CreateFromPrefilteredData`, `CreateFromImages` and `Parse` are static methods attached only inside `RegisterCubeTexture()` — the side effect run by the `cubeTexture.ts` wrapper. After the pure-barrel work, several modules import `CubeTexture` from `cubeTexture.pure` (side-effect-free) and called those statics directly, so they were `undefined` at runtime. ## Fix Use the exported pure functions instead of the dynamically-attached statics: - `Helpers/environmentHelper.ts` → `CubeTextureCreateFromPrefilteredData` - `Loading/Plugins/babylonFileLoader.pure.ts` → `CubeTextureParse`, `CubeTextureCreateFromPrefilteredData` - `Materials/Node/Blocks/PBR/refractionBlock.pure.ts` → `CubeTextureParse` - `Materials/Node/Blocks/Dual/reflectionTextureBaseBlock.pure.ts` → `CubeTextureParse` The `CubeTexture` class import is kept where the constructor / type is still used. This also fixes the same latent crash for `.babylon` environment loading and NME refraction/reflection deserialization on the pure path. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e instead of source scanning (BabylonJS#18632) ## Problem When a scene playground is loaded into the Flow Graph Editor's preview panel, the editor figures out whether the playground builds its flow graph from a snippet (e.g. `ParseFlowGraphCoordinatorFromSnippetAsync("#ABC123#0", { scene })`) and, if so, auto-opens that snippet in the editor — replacing whatever graph the user currently has open. Detection was a plain regex over the **raw** playground source that did not strip comments. As a result, an **inert preview scene** with a commented-out `ParseFlowGraphCoordinatorFromSnippetAsync(...)` line still forced a graph to load, silently clobbering the graph the user explicitly opened in the editor. A `...FromSnippetAsync` substring inside a string/URL literal could also be mis-detected. ## Approach Rather than parsing source text and trying to reason about comments and string literals, this detects the reference **at runtime**, since commented-out (and string-literal) code never executes. Before the preview snippet runs, the editor wraps the global `BABYLON` `...FromSnippetAsync` loaders so the snippet id is recorded from the call that actually executes, then restores the originals once the scene is ready. The wrappers delegate to the originals, so the playground still builds its graph exactly as before. The wrappers are installed **before `createEngine`**, which is the right window for both execution modes (verified against the snippet loader): - **script-mode** snippets call `BABYLON.ParseFlowGraphCoordinatorFromSnippetAsync(...)` on the global directly at `createScene` time; - **TS-mode** snippets import from `@babylonjs/core`, which the snippet loader resolves through a synthetic proxy module that copies `globalThis.BABYLON[name]` the first time module evaluation is triggered — and that evaluation happens lazily on the first `createEngine` call. This eliminates both bug classes by construction: - a commented-out loader never runs → never captured; - a `...FromSnippetAsync` substring inside a string/URL literal never runs → never captured. Genuinely active loaders still auto-open their graph for editing, and first-call-wins ordering is preserved. ## How to use From a user's perspective in the Flow Graph Editor, the behavior is driven entirely by whether the playground's loader call actually runs: - **Edit a graph that a scene loads at runtime** — load a scene playground whose code *actively* calls `await ParseFlowGraphCoordinatorFromSnippetAsync("#ABC123#0", { scene })`. The editor captures `#ABC123#0` as it executes and auto-opens that flow graph for editing; saving publishes a new version of that same snippet. (Unchanged behavior.) - **Use a scene purely as a preview, without it touching your open graph** — comment out the loader in the playground: ```ts // await ParseFlowGraphCoordinatorFromSnippetAsync("#ABC123#0", { scene }); ``` The scene still loads and renders in the preview panel, but because the commented line never executes, nothing is captured and the graph you currently have open in the editor is left untouched. The same holds if the only occurrence of `...FromSnippetAsync` is inside a string/URL literal. You no longer have to scrub every snippet reference out of the playground to use it as an inert level preview. No new UI, settings, or API surface — the distinction is simply "does the loader run or not." ## Changes - New pure helper `flowGraphSnippetCapture.ts` exporting `CaptureFlowGraphSnippetId(namespace)`, which wraps the matching loaders, records the first live call's id, and exposes a `restore()`. - `scenePreviewComponent` installs the capture before `createEngine`, restores it in a `finally` after `whenReadyAsync`, and passes the captured id to `_tryLoadReferencedFlowGraphAsync`. The old source-scan method was removed. ## Scenario covered An inert preview scene with a commented-out `ParseFlowGraphCoordinatorFromSnippetAsync(...)` line no longer clobbers the graph the user has open in the editor. ## Tests `packages/tools/flowGraphEditor/test/unit/flowGraphSnippetCapture.test.ts` (vitest), using spies on a fake `BABYLON` namespace: - live loader call → captures id and still delegates to the original - no loader call (commented-out / inert scene) → null - first-call-wins across multiple loaders - wraps every matching loader (`...FromSnippetAsync` family), not just the coordinator one - ignores non-loader functions and non-function properties - ignores empty / non-string ids - restores the originals - safe no-op when the namespace is missing ## Validation - `npx vitest run --project=unit .../flowGraphSnippetCapture.test.ts` → 8/8 pass - `tsc -b packages/tools/flowGraphEditor/tsconfig.build.json` → clean - eslint + prettier clean on changed files --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… Enhanced TS Config (BabylonJS#18626) ### gitignore.ts - every scaffolded project now gets a sensible `.gitignore` out of the box. ### sceneCode.ts - Replaces `BABYLON.AppendSceneAsync("https://assets.babylonjs.com/meshes/boombox.glb", scene)` with `BABYLON.CreateBox("box", {}, scene)` — no network dependency for the default scene, avoiding potential connectivity issues in certain regions. ### indexHtml.ts - **`display: block`** — `<canvas>` is an inline-replaced element by default. Setting it to `display: block` eliminates the small gap below the canvas caused by inline element baseline alignment. Without this, there is often a 3–4px whitespace strip at the bottom of the page. - **`100dvw` / `100dvh` instead of `100%`** — The dynamic viewport units (`dvw` / `dvh`) account for mobile browser UI changes (address bar appearing/disappearing), unlike `100%` which depends on parent element size. Using percentage-based sizing required setting `width: 100%; height: 100%` on both `html` and `body` as ancestor chain. With `dvw`/`dvh`, the canvas sizes directly against the viewport, eliminating the need for `overflow: hidden` on `html, body` and simplifying the CSS. - **Removing `overflow: hidden` from `<html>` and `<body>`** — No longer needed since the canvas uses viewport units directly and the body has zero margin/padding. Worth noting that the old `overflow: hidden` approach only masked the overflow symptom — the canvas content was still actually overflowing; it just wasn't visible. ### tsconfig.json - **`"noEmit": true` replacing `"outDir": "./dist"`** — `vite build` already outputs to `dist` by default. With `"noEmit": true`, `tsc` is used strictly as a type-checker (`tsc && vite build`); it emits nothing, and Vite alone handles all bundling. The `outDir` setting is therefore irrelevant and was removed. - **Added `"vite/client"` to types** — This provides TypeScript with type definitions for Vite-specific features such as `import.meta.env`, static asset imports (e.g., `import logo from './logo.png'`), HMR API (`import.meta.hot`), and the `?url` / `?raw` query suffixes. Without this, the TypeScript compiler would flag Vite-imported assets as type errors.
…18506) ## Context The visualization test runner has special-case logic that, after the per-test `renderCount` is exhausted, keeps rendering until every `AdvancedDynamicTexture` reports `guiIsReady()` and then renders one more frame. This was [introduced in BabylonJS#13475](BabylonJS#13475) to give GUI-based PGs time to finish loading before the screenshot is captured. It doesn't actually solve the race for PGs that fire-and-forget `parseFromSnippetAsync` / `parseFromURLAsync`: while the snippet fetch is still in flight, the ADT root container has zero children, `Container.isReady()` iterates an empty array and returns `true`, so `guiIsReady()` is satisfied immediately. The gate only adds a settle frame for image loads on controls that are *already* in the tree. It also bakes GUI-specific knowledge into a generic test runner. The correct contract is: a PG whose setup is async returns `Promise<Scene>` from `createScene` and the runner awaits it. The runner already does `await (createScene(engine, canvas) ?? createScene())`, so no runner-side framework change is needed beyond removing the gate. ## Changes - Drop the `guiIsReady()` gate from both viz runners: - `packages/tools/tests/test/playwright/visualizationPlaywright.utils.ts` - `packages/tools/testTools/src/visualizationUtils.ts` - Update the two PGs that needed the workaround to actually follow the `Promise<Scene>` contract, by saving new revisions: - `#YS93KY#0` → `#YS93KY#1` (Load GUI snippet with unicode) — `return adv.parseFromSnippetAsync("#KHPNS9").then(() => scene);` - `#ERVGT5#0` → `#ERVGT5#1` (Parse GUI json with unicode) — `return adv.parseFromURLAsync(...).then(() => scene);` - Pin the BJS viz config to the new revisions. ## Notes - The `AdvancedDynamicTexture.guiIsReady()` / `onGuiReadyObservable` API itself is **not** removed — only its usage in the test runner. It is still used internally by `FrameGraph/guiTask.ts` and remains available to anyone who wants it. - BabylonNative was hitting the same flake on slow CI runners ([BabylonNative#1716](BabylonJS/BabylonNative#1716) carries a temporary BN-side workaround). Once this PR lands and a fresh nightly UMD ships, that workaround will be removed in BN. [Created by Copilot on behalf of @bghgary] --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…eras (BabylonJS#18634) > 🤖 *This PR was created by the create-pr skill.* Holding two perpendicular movement keys applied each key's full impulse independently, making diagonal keyboard movement ~1.41x (√2) faster than a single axis. This PR accumulates the per-frame direction across pressed keys and normalizes it before applying, matching the existing `GeospatialCamera` keyboard fix. **Cameras fixed:** - `FreeCamera` — translation (WASD/arrows) - `FlyCamera` — translation - `ArcRotateCamera` — rotate and pan `FreeCamera` rotation keys and `ArcRotateCamera` zoom remain per-key (1D axes, unaffected). **Motivation:** Reported on the forum — [Moving camera diagonally (keyboard) is ~40% faster](https://forum.babylonjs.com/t/moving-camera-diagonally-keyboard-its-40-faster/63704/2). **Tests:** Added unit tests asserting diagonal movement distance ≈ single-axis distance for all affected cameras. --------- Co-authored-by: Georgina <gehalper@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Summary Applies `npm audit fix` — lockfile-only changes bumping transitive dependencies to patched versions. ## Changes `package-lock.json` only (60 insertions / 60 deletions), patch-level bumps: - `js-yaml` 3.14.2 → 3.15.0 - `nx` / `@nx/*` platform binaries 22.7.5 → 22.7.6 - `form-data` 4.0.5 → 4.0.6 - `hasown` 2.0.2 → 2.0.4 - `tmp` 0.2.6 → 0.2.7 No `package.json` manifest changes. No source changes.
…ng (BabylonJS#18646) ## Problem In the Flow Graph Editor, dragging a box moved the node but its connections stayed frozen in place instead of stretching along with it. ## Root cause Link redraws (`GraphNode._refreshLinks`) early-out whenever `GraphCanvasComponent._isLoading` is `true`. The editor calls `build()` on mount. With an empty graph (no saved `_editorData`), `build()` defers layout to a `setTimeout` → `sortGraph()`, which **early-returned on zero nodes without clearing `_isLoading`**. So the flag was stranded `true` from the very first mount, and every subsequent node drag skipped the link (and frame) update — the box moved, the wires didn't. This is specific to the Flow Graph Editor; NME/NGE/etc. always clear the flag via `reOrganize`, so they aren't affected. ## Fix - Clear `_isLoading = false` synchronously in `build()`'s no-editorData branch (bulk node creation in `loadGraph()` is already done by then), so the flag is never held across the deferred `sortGraph()` — which can be skipped by a newer build, a changed flow graph, or an empty graph. - Defensively clear `_isLoading` in `sortGraph()`'s zero-node early return. ## Testing - Added a Playwright regression test that adds two blocks, connects them, drags one, and asserts the SVG link geometry follows the moved node (plus a `getLinkPaths()` helper in `fge.utils.ts`). - Verified the test **fails without the fix** (link `d` attribute identical before/after drag) and **passes with it**. - `prettier`, `tsc -b`, and `eslint` pass on the changed source; all 124 flowGraphEditor unit tests pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…havior change) (BabylonJS#18631) ## Summary PR1 of the multi-PR effort to migrate Babylon.js to TC39 Stage 3 decorators. **Infrastructure only — no runtime behavior change.** This PR swaps the serialization decorator metadata **store** from class-name-keyed global maps to per-constructor `Symbol.metadata`, while **keeping `experimentalDecorators: true`** in tsconfig. Serialization output and all unit tests are unchanged. ## What changed - **`packages/dev/core/src/Misc/decorators.functions.ts`** — `GetDirectStore`/`GetMergedStore` now read/write the store on each class constructor's own `Symbol.metadata` instead of the `DecoratorInitialStore`/`MergedStore` class-name-keyed maps. - Experimental decorators call with `(prototype, propertyKey)`, so the store is attached to `prototype.constructor[Symbol.metadata]`. - Each constructor gets its **own** metadata object whose prototype mirrors the class hierarchy (`Object.create(parentMetadata)`), so `GetMergedStore` can walk the metadata prototype chain. - Merge order is preserved (most-derived first, parents overwrite) to match the original class-name-keyed merge. - Merged store is memoized via a `WeakMap` keyed on the metadata object. - **Public signatures of `GetDirectStore`/`GetMergedStore` are unchanged**, so all serialization consumers compile untouched. - **`packages/dev/core/src/Misc/symbolMetadataPolyfill.ts`** (new) — idempotently defines `Symbol.metadata` as a one-time side effect. `decorators.functions.ts` also self-heals defensively for import orders/runtimes where the entry-point polyfill hasn't run yet. - **`packages/dev/core/src/index.ts`** — imports the polyfill for side effect as the first statement, before any decorated class is evaluated. - Tree-shaking manifest + `@babylonjs/core` `sideEffects` regenerated to record the polyfill's top-level side effect. ## Out of scope (later PRs) No call-site changes, no `accessor` keyword, no `experimentalDecorators` flip, no UMD target changes. ## Validation - `npm run format:check` ✅ - `npm run lint:check` (eslint + typecheck + `check:treeshaking` all-packages + `check:side-effects-sync`) ✅ — all 16 checks pass - `npm run test:unit` ✅ — 4062 passed, 1 expected fail, 60 skipped. Serialization tests (core + gui) pass. (The 10 `EnvironmentTeardownError` rejections are pre-existing lazy-shader teardown flakiness, unrelated to this change.) 🤖 Generated with Copilot CLI --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…eams (BabylonJS#18645) ## WebGPU support for WebXR — Phase 0 (behavior-preserving, WebGL-only refactor) Part of BabylonJS#18637 (epic BabylonJS#18635). Phase 0 introduces API-agnostic seams into Babylon WebXR so a future WebGPU-backed XR session (layers-only, `XRGPUBinding`, `engine.wrapWebGPUTexture`) can plug in later. It is a **strict no-op for existing WebGL2 XR** — no functional change, no WebGPU/`XRGPUBinding` code. Public API surface is unchanged (additive/backward-compatible only). > This PR consolidates what was originally staged as three stacked PRs (BabylonJS#18643 → BabylonJS#18644 → BabylonJS#18645). They were stacked onto feature branches, which meant the Azure snapshot pipeline (PR trigger is `master`-only) never produced a preview build for the combined change. Retargeting the tip branch to `master` yields a single testable, snapshot-backed, mergeable PR containing all of Phase 0. ### 1) Engine typing - Promotes `framebufferDimensionsObject` (public setter + protected backing field) from `ThinEngine` up to `AbstractEngine`. Additive and backward-compatible: `ThinEngine`/`Engine` keep consuming/overriding it (Engine keeps the `onResize` notification; ThinEngine keeps the `_gl` fallback in `getRenderWidth/Height`), WebGPU inherits harmless default storage. No new getter — the write-only read semantics are preserved exactly. - Retypes `WebXRSessionManager._engine` and `WebXRLayerRenderTargetTextureProvider._engine` from `Engine` to `AbstractEngine`; removes the `scene.getEngine() as Engine` casts, plus the leftover `as ThinEngine` cast in `webXRExperienceHelper.ts`. ### 2) RT-provider split - `WebXRLayerRenderTargetTextureProvider` (base) is now **graphics-API-agnostic** (no WebGL imports) and exposes two protected hooks: - `_createRenderTargetTextureShell(width, height, multiview)` — builds the `RenderTargetTexture`/`MultiviewRenderTarget` with the correct sample count. - `_createRenderTargetTextureInternal(...)` — assembles a `RenderTargetTexture` from already-wrapped `InternalTexture`s with no graphics-API types. **This is the seam a future GPU backend uses** (`wrapWebGPUTexture` → `_createRenderTargetTextureInternal`). Intentionally unused in Phase 0. - New WebGL-specific abstract provider `WebXRWebGLRenderTargetTextureProvider` owns the WebGL-typed `_createRenderTargetTexture` / `_createInternalTexture` (`WebGLHardwareTexture` via `engine._gl`). The **exact original creation order is preserved** (framebuffer assigned before `setTexture`). - All WebGL-backed subclasses repointed to the new base (WebGL-layer, Composition→Projection, Native). ### 3) Type generalization + graphics-binding seam - `WebXRRenderTarget` is now generic with WebGL defaults: `WebXRRenderTarget<TContext = WebGLRenderingContext, TLayer extends XRLayer = XRWebGLLayer>`. Used without type args it resolves to the exact previous shape (compile- and runtime-compatible). Stays public. - `WebXRManagedOutputCanvas` context/layer creation factored into overridable protected seams `_createXRCompatibleRenderingContext()` and `_createXRLayer()`; WebGL behavior is byte-for-byte unchanged. Kept off `AbstractEngine`'s public surface deliberately (WebGPU XR is layers-only and may never use a managed-output baseLayer). - New side-effect-free `WebXRGraphicsBinding` abstraction (`IWebXRGraphicsBinding` + `WebXRGraphicsBindingType` + WebGL impl `WebXRWebGLGraphicsBinding`) hiding `XRWebGLBinding` vs a future `XRGPUBinding`, plus `WebXRSessionManager._getGraphicsBinding()`. **Marked `@internal` and not barrel-exported** — introduced but unconsumed until Phase 4 proves the per-op shape, so it stays off the public API surface. ### Deliberate non-changes / scope - The graphics-binding seam and `_createRenderTargetTextureInternal` are **introduced but not yet consumed**. XR features (`WebXRLayers`, `WebXRRawCameraAccess`, `WebXRDepthSensing`, `WebXRLightEstimation`, `WebXRSpaceWarp`) keep constructing `XRWebGLBinding` directly — porting them is **Phase 4**. - `WebXRLayerWrapper`/`WebXRLayerType` were already API-agnostic; the `layerType == "XRWebGLLayer"` foveation guard stays for behavior preservation. - Multiview color/depth **array** path (`_colorTextureArray`/`_depthStencilTextureArray`) still lives in the WebGL `_createRenderTargetTexture`; the GPU-agnostic `_createRenderTargetTextureInternal` only handles the single-`InternalTexture` path today. Equivalent GPU path is tracked for **Phase 2** (BabylonJS#18640). ### Validation - `tsc -b tsconfig.devpackages.json` ✅ - `format:check` (prettier) ✅ / `lint:check` (eslint + ratchets) ✅ - XR + Engines unit tests ✅ - `check:treeshaking` (16 checks) ✅ / `check:side-effects-sync` (all 4 packages) ✅ - Zero net public-API additions (binding seam is `@internal`; `framebufferDimensionsObject` is additive on `AbstractEngine`; `WebXRRenderTarget` generics are default-compatible). - WebGL2 XR desktop smoke passed (scene renders, session manager constructs under the `AbstractEngine` retype, context seam yields a real `WebGL2RenderingContext`). In-headset Quest Browser WebGL2 XR smoke is the final gate before merge. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
) ## What Upgrades the `vite` pin from `6.4.3` to `8.1.3` across the root and the workspaces that depend on Vite directly (`devHost`, `tools/viewer`, `public/@babylonjs/viewer`). Vite 8 ships the Rust-based **Rolldown** bundler as its default (and only) bundler, replacing Rollup + esbuild. This is the officially supported path — the transitional `rolldown-vite` package was only a bridge for the Vite 7 line and is not needed here. No `npm:` alias or `overrides` entry is required. ## Why Vite only builds the **tool apps** (editors, playground, sandbox, viewer, devHost, inspector-v2, vsm) — not the core libraries (those use `tsc`/nx/UMD). The goal is to shave time off the tool-build portion of CI. ## Measured impact Local production builds (`vite build`), matched runs, equivalent output: | Tool | Vite 6.4.3 | Vite 8.1.3 (Rolldown) | Speedup | |---|--:|--:|--:| | playground (Monaco) | 12.92s | 3.35s | ~3.9× | | flowGraphEditor | 8.06s | 1.18s | ~6.8× | | nodeEditor | 3.29s | 0.60s | ~5.5× | | nodeGeometryEditor | 3.07s | 0.63s | ~4.9× | | nodeParticleEditor | 3.08s | 0.61s | ~5.0× | | guiEditor | 2.77s | 0.55s | ~5.0× | | sandbox | 0.55s | 0.20s | ~2.8× | Bundle sizes are equivalent (e.g. nodeEditor JS `1254.13 kB` → `1252.23 kB`; gzip slightly smaller). ## Validation - All standalone tool builds pass on Vite 8. - Dev server (`serve`/HMR) and TS transform verified on nodeEditor (ready in ~126ms). - `npm install` resolves cleanly with no peer-dependency conflicts. ## Notes / follow-ups - This is a **Vite 6 → 8 jump** (two majors). Watch the full CI tool matrix on this PR. - Non-fatal deprecation warnings remain from `@vitejs/plugin-react` (recommends `@vitejs/plugin-react-oxc`). Migrating that plugin is a potential follow-up for additional speed. - The `package-lock.json` diff reflects the real dependency-tree change (Rolldown native binaries replacing rollup/esbuild transitives). --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
… 1, BabylonJS#18638) (BabylonJS#18650) ## Phase 1 (BabylonJS#18638) — WebGPU-compatible XR session + XRGPUBinding plumbing Closes BabylonJS#18638 (Phase 1 of the WebGPU-for-WebXR epic BabylonJS#18635 — the epic stays open for Phases 2–5). Part of the WebGPU-for-WebXR effort (epic BabylonJS#18635). **Plumbing only — no per-frame WebGPU XR rendering (Phase 2), no camera/NDC (Phase 3), no feature rewiring (Phase 4).** WebGL2 XR behavior is byte-for-byte identical; every WebGPU path is gated behind `AbstractEngine.isWebGPU` and is unreachable for WebGL engines. > Consolidated from an initial 3-PR stack into this single PR (kept as focused commits for readability). Supersedes BabylonJS#18651 and BabylonJS#18652. Rebased onto master after Phase 0 (BabylonJS#18645) merged. ### What's in here **1. TS typings + adapter opt-in** - New companion `packages/dev/core/src/LibDeclarations/webxr.webgpu.d.ts` (mirrors the existing `webxr.nativeextensions.d.ts` split so the large community-maintained `webxr.d.ts` stays untouched). Declares `XRGPUBinding`, `XRGPUSubImage`, and the `XRGPU*LayerInit` dictionaries. Base XR layer/sub-image types come from `webxr.d.ts`; `GPURequestAdapterOptions.xrCompatible` stays in `webgpu.d.ts` (not duplicated). - Documented `xrCompatible` on `WebGPUEngineOptions`. It is inherited from `GPURequestAdapterOptions` and already flows end-to-end (`initAsync` passes `_options` straight to `navigator.gpu.requestAdapter()`), so this is JSDoc/discoverability only — it notes the flag must be set at adapter-request/engine-construction time (WebGPU has no post-hoc "make XR compatible" step). **2. `@internal` WebGPU graphics binding + wiring** - `WebXRWebGPUGraphicsBinding` implementing the Phase-0 `IWebXRGraphicsBinding` seam, wrapping `new XRGPUBinding(session, device)`, plus a `WebXRGraphicsBindingType.WebGPU` enum member. Minimal/introduced-but-unused — per-frame layer/sub-image ops are deferred to later phases. - `WebXRSessionManager._getGraphicsBinding()` branches on `AbstractEngine.isWebGPU`: WebGPU engine → `XRGPUBinding`-backed binding; everything else → the existing `XRWebGLBinding`-backed one. The `GPUDevice` is read from `WebGPUEngine._device` via a **type-only** import + cast (mirrors Phase 0's `(engine as ThinEngine)._gl`), so there is no runtime coupling/side effect. Kept `@internal` and out of `index.ts`/`pure.ts`. **3. WebGPU-compatible session gating** - `initializeSessionAsync` requests the `'webgpu'` feature descriptor as a **required** feature when the engine is WebGPU (a WebGPU engine cannot fall back to a WebGL XR session; the native `requestSession` rejection is left to propagate to the caller, not swallowed). The WebGL branch leaves the `XRSessionInit` object untouched (same reference passed through — asserted in a test). - `webXRExperienceHelper.enterXRAsync` skips the `baseLayer`/`XRWebGLLayer` path for WebGPU, since a WebGPU-compatible session is **layers-only**. **4. Doc-only clarification** of the ENTERING_XR end-state (see below) — comment-only, no logic change. ### ✅ Hardware-validated Phase 1 end-state (not a regression) Validated on Meta Quest Browser with the experimental `XRGPUBinding` flag, against this PR's snapshot build. The precise Phase 1 end-state is: > The native WebGPU XR session **enters** — an `xrCompatible` WebGPU engine is created, the `'webgpu'` feature is accepted and appears in `session.enabledFeatures`, `baseLayer` is correctly skipped, and `enterXRAsync` resolves without throwing. **But** because Phase 1 attaches **no layer** (the `XRProjectionLayer` is Phase 2), the session receives **no `requestAnimationFrame` callbacks**, so `onXRFrameObservable` never fires and Babylon's `WebXRState` **stays at `ENTERING_XR` and never reaches `IN_XR`** (which is gated on the first frame). This is the correct Phase 1 result, **not a regression**. This "no layer → no frame" behavior was confirmed both through Babylon and at the **raw-browser level**: a bare `navigator.xr` `immersive-vr` session requested with `requiredFeatures: ['webgpu']` is granted `'webgpu'`, yet a `requestAnimationFrame` with no layer set yields **zero callbacks**. So the stall is inherent to the layers-only WebGPU-XR design and the WebXR spec, not a bug in this plumbing. Reaching `IN_XR` / actually rendering is **Phase 2** (projection-layer RTT provider via `wrapWebGPUTexture`). **Exit is clean from `ENTERING_XR`:** ending the session via its native `end` path (e.g. the headset system menu) fires `onXRSessionEnded` regardless of `WebXRState`, restoring the framebuffer/render loop/camera and returning `WebXRState` to `NOT_IN_XR` with no hang or leak (the `@internal` binding is nulled on end). Note: the programmatic `exitXRAsync()` is a no-op from `ENTERING_XR` (its pre-existing `state !== IN_XR` guard, unchanged by this PR) — a no-op, not a hang; the session remains exitable via the native path. A unit test confirms `updateRenderState` with neither `baseLayer` nor `layers` does not throw. /cc @RaananW. ### Spec grounding Declarations/behavior are taken from the immersive-web/WebXR-WebGPU-Binding explainer + proposed IDL: https://github.com/immersive-web/WebXR-WebGPU-Binding/blob/main/explainer.md — including the `'webgpu'` feature descriptor, the layers-only rule (no `baseLayer`), and the `XRGPUBinding(session, device)` shape requiring an `xrCompatible` adapter. ### Constraints honored - WebGL2 XR byte-for-byte identical; all WebGPU logic gated behind `isWebGPU`. - No `.pure.ts` split, no top-level side effects (confirmed: no side-effect-manifest churn on any commit). - **Zero net public-API additions** — the binding is `@internal` and out of the barrels; `xrCompatible` is an inherited, now-documented option. ### Testing - `tsc -b tsconfig.devPackages.json` exit 0. - `npm run format:check` ✅ ; `npm run lint:check` ✅ (eslint + all 16 tree-shaking checks + side-effects-sync across core/gui/loaders/serializers). - XR unit gate `vitest run --project=unit -t "XR"`: **260 passed**. `webXRSessionManager.test.ts` cases cover binding selection (WebGL vs WebGPU), binding caching, the uninitialized-session guard, `'webgpu'` injected only for WebGPU engines (existing required features preserved, no duplicates), WebGL init passed through unchanged (same object ref), `updateRenderState` with no layer not throwing, and the native session-`end` handler cleaning up (nulls the graphics binding, clears `inXRSession`, notifies `onXRSessionEnded`) even when no frame ever arrived. New `webXRExperienceHelper.test.ts` covers the no-frame WebGPU lifecycle: state stays at `ENTERING_XR` with no frame, `exitXRAsync()` from `ENTERING_XR` is a clean no-op (does not touch the session manager, state unchanged), and a native session end from `ENTERING_XR` returns state to `NOT_IN_XR` with no hang or leak. - **Hardware-validated** on Meta Quest Browser (XRGPUBinding flag) against the PR snapshot — see the end-state section above. - Note: the unrelated `smartFilterBlocks` vitest suite fails to import in this environment (missing generated `.block.js` artifacts) — pre-existing, not touched by this PR. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The Viewer adds a default hemispherical light if the model isn't PBR. Currently, it doesn't consider OpenPBRMaterial so this PR adds that check. --------- Co-authored-by: Ryan Tremblay <ryan.tremblay@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
LightingVolume binds the shadow generator's shadow map depthStencilTexture - a depth-format texture - as the shadowMap input of the lightingVolume compute shader. Declare that binding as texture_depth_2d instead of texture_2d<f32>, and drop the .r swizzle on the textureLoad result since textureLoad on texture_depth_2d returns a scalar f32. The loaded value is the same depth that .r previously carried, so far-plane fitting results are unchanged. This completes the compute depth sample-type support introduced in PR BabylonJS#18460: _GetComputeTextureSampleType classifies this texture as bind group layout sampleType "depth", and WebGPU validation requires a texture_depth_2d WGSL declaration for a "depth" layout entry. With the previous texture_2d<f32> declaration, createComputePipeline fails validation (observed in Chromium and wgpu-native) as soon as the lighting volume compute shaders run with an explicit pipeline layout. The change is also safe with the default auto layout: texture_depth_2d derives sampleType "depth", which is valid for the depth-aspect view Babylon binds, so behavior is identical there. This came from the Hill Valley GLTF/NativeXR AR Portal validation pass. (cherry picked from commit a81ccfb)
matthargett
force-pushed
the
claude/jolly-allen-k9ldki
branch
3 times, most recently
from
July 4, 2026 23:08
443da4e to
5c45e6c
Compare
|
This pull request has been marked as stale because it has been inactive for more than 14 days. Please update to "unstale". |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fork staging PR for the upstream submission (upstream compare link).
Summary
shadowMapbinding oflightingVolume.compute.fxastexture_depth_2dinstead oftexture_2d<f32>, and drop the.rswizzle sincetextureLoadontexture_depth_2dreturns a scalarf32.Why
LightingVolumebinds the shadow generator's shadow mapdepthStencilTexture— a depth-format texture (TEXTUREFORMAT_DEPTH32_FLOAT) — as theshadowMapinput of this compute shader (lightingVolume.pure.ts).This completes the compute depth sample-type support introduced in BabylonJS#18460:
_GetComputeTextureSampleTypeclassifies that texture as bind group layout sampleType"depth", and WebGPU pipeline validation requires atexture_depth_2dWGSL declaration to match a"depth"layout entry. With the currenttexture_2d<f32>declaration,createComputePipelinefails validation (observed in Chromium and wgpu-native) as soon as the lighting volume compute shaders run with an explicit pipeline layout.Under the default auto layout the current declaration only works by accident:
texture_2d<f32>used withtextureLoadderives sampleType"unfilterable-float", which a depth-aspect view happens to be compatible with. The fix is the semantically correct binding for a depth texture and is equally valid under the auto layout (texture_depth_2dderives sampleType"depth"), and the loaded scalar is the same depth value.rpreviously carried, so far-plane fitting results are unchanged.Repro
Playground (run with the WebGPU engine): https://playground.babylonjs.com/#JE6IH3
It dispatches the lightingVolume far-plane kernel against a shadow generator's
depthStencilTexturefour ways (shipped/fixed WGSL x auto/explicit layout). On Babylon.js 9.12.0 + Chrome Canary, the shippedtexture_2d<f32>declaration under the explicit pipeline layout failscreateComputePipelinewith:With
texture_depth_2d, the pipeline builds under both explicit and auto layouts, and the storage-buffer readback is bit-identical to the previous.rpath (the playground compares the two outputs element by element), verifying far-plane fitting depths are unchanged. The snippet inspects the running build's shader store and self-reports once a build ships the fix, so it can be re-run post-merge as confirmation.Device context
This came from the Hill Valley GLTF/NativeXR AR Portal validation pass.
Validation
createComputePipelinevalidation error (the bug)texture_2d<f32>.routput, with real geometry depths present (min depth < 1.0)lightingVolume.compute.tsis produced at build time, and.prettierignoreexcludesShadersWGSL, so no other source files change.